/***********************************************************************************************
 * Javascript classes common to all Web application supported by LM
 * These classes do not refer to any application-specific resource
 * 
 * Content:
 * - String utilities (not a class but extensions of the String class )
 * - Printer   : printout any div
 * - Modalinfo : open any kind of dialog boxes
 * - Processing: Progress bar open while Ajax calls
 * - Out       : Print message in the console with or without stack trace
 * - Location  : to be called to process data downloading
 * 
 * Required external resources 
 * - jquery-ui
 * - jquery.alerts
 * - jquery.datatables
 * - jquery.simplemodal
 * - jquery.prints
 * 
 * Laurent Michel 20/12/2012
 */

/**
 * Adding some useful String methods
 */
if(!String.prototype.startsWith){
	String.prototype.startsWith = function (str) {
		return !this.indexOf(str);
	};
};
if(!String.prototype.endsWith){
	String.prototype.endsWith = function(suffix) {
		return (this.indexOf(suffix, this.length - suffix.length) !== -1);
	};
};

if(!String.prototype.hashCode){
	String.prototype.hashCode = function(){
		var hash = 0;
		if (this.length == 0) return code;
		for (var i= 0; i < this.length; i++) {
			charac = this.charCodeAt(i);
			hash = 31*hash+charac;
			hash = hash & hash; 
		}
		return hash;
	};
};
if(!String.prototype.trim){
	String.prototype.trim = function(){
	} ;
};
/**
 * return the last node of file pathname
 */
if(!String.prototype.xtractFilename){
	String.prototype.xtractFilename = function(){
		var re = new RegExp(/(\w:)?([\/\\].*)*([\/\\](.*))/);
		var m = this.match(re);
		if( m == null ) {
			return {path: "", filename: this};
		}
		else {
			return {path: m[1], filename: m[m.length-1]};
		}
	} ;
}

/**
 * Quotes the element of a data treepath which cannot be understood by SQL
 * e.g. 
 * "alert(
    'vizls.II/306/sdss8'.quotedTableName() + '\n' +
    'vizls.II/306/sdss8.OBS'.quotedTableName() + '\n' +
    'viz2.J/other/KFNT/15.483/jovsat'.quotedTableName() + '\n' +
    'viz2.J/other/KFNT/15.483/jovsat.OBS'.quotedTableName());
 */

if (!String.prototype.quotedTableName) {
	String.prototype.quotedTableName = function () {
		var results = this.split(".");
		var tbl = new Array(results[0]);
		if (results.length > 1) {
			for (var i = 1; i < results.length; i++) {
				/*
				 * If a field starts with a dot we suppose that this dot is not a treepath separator but
				 * that is belongs to that field since table names or columns names do not spart with a number
				 */
				if (results[i].match(/^[0-9].*/)) {
					tbl[tbl.length - 1] += "." +results[i];
				} else {
					tbl.push(results[i]);
				}
			}
		}
		for (var j = 0; j < tbl.length; j++) {
			if (!tbl[j].match(/^[a-zA-Z0-9][a-zA-Z0-9_]*$/)) {
				tbl[j] = '"' + tbl[j] + '"';
			}
		}
		return tbl.join(".");
	};
}


/*****************************************************
 * Some global variables
 */

var rootUrl = "http://" + window.location.hostname +  (location.port?":"+location.port:"") + window.location.pathname;

/*
 * make sure that Modalinfo are on top of modals
 * and that Processing widows are on top of Modalinfo
 */
var zIndexModalinfo = 3000;
var zIndexProcessing = 4000;
/*
 * Types supported by the previewer based on iframes
 */
var previewTypes = ['text', 'ascii', 'html', 'gif', 'png', 'jpeg', 'jpg', 'pdf'];


/*****************************************************************************************************
 * Object managing printer features
 */
Printer = function() {
	/*
	 * Public functions
	 */
	var getPrintButton = function(divToPrint) {
		var retour =  "<a href='#' onclick='Printer.printDiv(\"" + divToPrint + "\");' class='printer'></a>";
		return retour;
	};
	var getSmallPrintButton = function(divToPrint) {
		var retour =  "<a href='#' onclick='Printer.printDiv(\"" + divToPrint + "\");' class='dlprinter'></a>";
		return retour;
	};
	var insertPrintButton = function(divToPrint, divHost) {
		$("#" + divHost).append(printer.getPrintButton(divToPrint));
	};
	var printDiv = function(divSelect) {
		var ele = $('#' + divSelect);
		if( !ele ) {
			Modalinfo.error("PRINT: the element " + divSelect +" doesn't exist");
		} else {
			Out.infoMsg(ele);
			ele.print();
		}		
	};
	/*
	 * exports
	 */
	var pblc = {};
	pblc.getPrintButton  = getPrintButton;
	pblc.getSmallPrintButton  = getSmallPrintButton;
	pblc.insertPrintButton = insertPrintButton;
	pblc.printDiv = printDiv;
	return pblc;
}();

/*****************************************************************************************************
 * Object opening a modal window centered on the screen and not draggable.
 * The content of the box must often be set after the box is open in order to have it accessible in the DOM.
 * In this case the real size must be computed again after the content is totally set.
 * -open: open the modal box
 * -resize: resize it according its real content.
 */
Modalpanel = function() {
	var divId = 'modalpanediv';
	var divSelect = '#' + divId;
	var height = 0;
	var width = 0;
	/*
	 * Private methods
	 */
	var initDiv = function() {
		if( $(divSelect).length != 0){	 
			$(divSelect).remove();
		}		
		$(document.documentElement).append("<div id=" + divId + " style='overflow: auto;display: none; width: auto;height: auto;'></div>");
	}; 
	/*
	 * Public methods
	 */
	var open = function(innerDivHtml, modalParams) {
		Out.debug("Open Modal Box");
		initDiv();
		$(divSelect).html(innerDivHtml);
		/*
		 * Build the modal
		 */
		$(divSelect).modal(modalParams);	
		resize();
	};
	var resize = function(){
		var heighthMax = $(window).height()*0.9;
		var widthMax = $(window).width()*0.9;
		/*
		 * Take the size of the content
		 */
		height =  $(divSelect).height();
		width = $(divSelect).width();
		/*
		 * Limit the size of the container to the maximum size allowed
		 */
		if(height > heighthMax) {
			$(divSelect).css("height", heighthMax);
			height = heighthMax;
		}
		if(width > widthMax) {
			$(divSelect).css("width", widthMax);
			width = widthMax;
		}			
		Out.debug("Resize Modal Box to " +  (8+ width) + "px x " + (8+ height) + "px");
		/*
		 * Resize it to fits the size constraints
		 * Add 8px for the borders: avoids double scroll-bars
		 */
		var smc = $("#simplemodal-container");
		smc.css('height', (8+ height)); 
		smc.css('width', (8+ width)); 
		$(window).trigger('resize.simplemodal'); 
	};
	/*
	 * exports
	 */
	var pblc = {};
	pblc.open = open;
	pblc.resize = resize;
	return pblc;

}();

/******************************************************************
 * This objet opens a modal panel just beneath the standard modal panel 
 * That way, it remains open when the Modalinfo panel closes
 */
Modalcommand = function() {
	var divId     = 'modalcommanddiv';
	var divSelect = '#' + divId;
	/*
	 * Privates functions
	 */
	var initDiv = function() {
		/*
		 * The div must be re-built at any time because 
		 * FF an Opera keep the wider size used before
		 */
		if( $(divSelect).length != 0){		
			$(divSelect).remove();
		}		
		$(document.documentElement).append("<div id=" + divId + " style='display: none; width: auto; hight: auto;'></div>");
	}; 
	/*
	 * Public functions
	 */
	var commandPanel = function (title, htmlContent, closeHandler) {
		initDiv();
		var chdl = ( closeHandler == null )? function(ev, ui)  {}: closeHandler;
		$(divSelect).html(htmlContent);
		$(divSelect).dialog({ modal: true
			, resizable: false
			, width: 'auto'
				, title: title 			                      
				, zIndex: (zIndexModalinfo -1)
				, close: chdl});
	};
	var setDivToggling = function(handler) {
		$(divSelect + " fieldset legend").click(function() {
			$(this).parent().find("div").first().toggle(handler);		  
		});
	};
	/*
	 * exports
	 */
	var pblc = {};
	pblc.commandPanel = commandPanel;
	pblc.setDivToggling = setDivToggling;
	return pblc;
}();

/*****************************************************************************************************
 * Object opening any types of dialog boxes
 */
Modalinfo = function() {
	var divId        = 'modalinfodiv';
	var divIdCnt =  divId + '_contnt';
	var divSelect = '#' + divId;
	/*
	 * Iframes are pushed on another div because they can not have interaction 
	 * with the current modal panel
	 */
	var iframeId     = 'iframeInfodiv';
	var iframeSelect = '#' + iframeId;
	/*
	 * Popup panel can be open from  dialog
	 */
	var popupId     = 'popupInfodiv';
	var popupSelect = '#' + popupId;
	/*
	 * Privates functions
	 */
	var initDiv = function() {
		/*
		 * The div must be re-built at any time because 
		 * FF an Opera keep the wider size used before
		 */
		if( $(divSelect).length != 0){		
			$(divSelect).remove();
		}		
		$(document.documentElement).append("<div id=" + divId + " style='display: none; width: auto; hight: auto;'></div>");
	}; 
	var initIframeDiv = function() {
		/*
		 * The div must be re-built at any time because 
		 * FF an Opera keep the wider size used before
		 */
		if( $(iframeSelect).length != 0){		
			$(iframeSelect).remove();
		}		
		$(document.documentElement).append("<div id=" + iframeId + " style='display: none; width: auto; hight: auto;'></div>");
	}; 
	var initPopupDiv = function() {
		/*
		 * The div must be re-built at any time because 
		 * FF an Opera keep the wider size used before
		 */
		if( $(popupSelect).length != 0){		
			$(popupSelect).remove();
		}		
		$(document.documentElement).append("<div id=" + popupId + " style='display: none; width: auto; hight: auto;'></div>");
	}; 
	var formatMessage = function(message) {
		var retour = "<span class=alert>" + message.replace(/\n/g, "<BR>") + "</span>";
		return retour;
	};
	var buildStandardPopup = function(logoClass, title, formatedMessage) {
		initPopupDiv();
		$(popupSelect).html("<div class=" + logoClass 
				+ "></div><div style='display: inline;'><span class=help>" 
				+ formatedMessage+ "</span></div>");
		$(popupSelect).dialog({  maxWidth: '50%'
			, width : 'auto'
				, title: title
				, resizable: false
				, modal: true
				, zIndex: zIndexModalinfo});

	};
	var getTitle = function (replacement, title, content){
		if( title == undefined ) {
			return replacement;
		} else {
			return title;
		}
	};
	/**
	 * Return the content of the object x as a user readable string
	 */
	var dump = function (x, indent) {
		var indent = indent || '';
		var s = '';
		if (Array.isArray(x)) {
			s += '[';
			for (var i=0; i<x.length; i++) {
				s += dump(x[i], indent);
				if (i < x.length-1) s += ', ';
			}
			s +=']';
		} else if (x === null) {
			s = 'NULL';
		} else switch(typeof x) {
		case 'undefined':
			s += 'UNDEFINED';
			break;
		case 'object':
			s += "{ ";
			var first = true;
			for (var p in x) {
				if (!first) s += indent + '  ';
				else s += "\n" + indent+ '  ';
				if( p != "id" && p != "$" ) s += p + ': ';
				s += dump(x[p], indent + '  ');
				if( p != "id" && p != "$" ) s += "\n";
				else s += " " ;
				first = false;
			}
			s += indent +'}';
			break;
		case 'boolean':
			s += (x) ? 'TRUE' : 'FALSE';
			break;
		case 'number':
			s += x;
			break;
		case 'string':
			if( x.lastIndexOf("http", 0) === 0 ) 
				x = decodeURIComponent(x);
			if( x.match(/\s/))
				s += '"' + x + '"';
			else 
				s += x;
			break;
		case 'function':
			s += '<FUNCTION>';
			break;
		default:
			s += x;
		break;
		}
		return s;
	};
	/*
	 * Public functions
	 */
	var dataPanel = function (title, htmlContent, closeHandler, bgcolor) {
		initDiv();
		var chdl = ( closeHandler == null )? function(ev, ui)  {$(divSelect).html("");}: closeHandler;
		if( bgcolor != null ) {
			$(divSelect).css("background-color", bgcolor);
		}
		$(divSelect).html(htmlContent);
		$(divSelect).dialog({ modal: true
			// , resizable: false
			, width: 'auto'
				//, maxHeight: '1000px'    
				, dialogClass: 'd-maxsize'

					, title: title 		  
					, zIndex: zIndexModalinfo
					, close: chdl});
	};
	/**
	 * htmlCOntent is used to fill the div whereas url is called when the view s expended toanother tab
	 */
	var urlDataPanel = function (title, htmlContent, url, closeHandler, bgcolor) {
		initDiv();
		var chdl = ( closeHandler == null )? function(ev, ui)  {$(divSelect).html("");}: closeHandler;
		if( bgcolor != null ) {
			$(divSelect).css("background-color", bgcolor);
		}
		var stitle =  "<span>Preview of " 
			+ title 
			+ " <a class=dldownload href='#' onclick='PageLocation.changeLocation(&quot;" 
			+ url + "&quot;, &quot;" + title 
			+ "&quot;);' title='Open in a new tab or download' style='display: inline-block;'></a></span>";
		$(divSelect).html(htmlContent);
		$(divSelect).dialog({ modal: true
			// , resizable: false
			, width: 'auto'
				//, maxHeight: '1000px'    
				, dialogClass: 'd-maxsize'

					, title: stitle 		  
					, zIndex: zIndexModalinfo
					, close: chdl});
	};

	var openIframePanel = function (url, title) {
		initIframeDiv();
		var fileName = url;
		var fullTitle =  "<span>Preview of " 
			+ ((!title)? fileName: title) 
			+ " <a class=dldownload href='#' onclick='PageLocation.changeLocation(&quot;" 
			+ url + "&quot;, &quot;" + fileName 
			+ "&quot;);' title='Open in a new tab or download' style='display: inline-block;'></a></span>";
		$(iframeSelect).html("<iframe style='width: 98%; height: 98%;'></iframe>");
		$(iframeSelect).dialog({ modal: true
			, resizable: true
			, width: ($(window).width()*0.9)
			, height: ($(window).height()*0.9)
			, title: fullTitle	                      
			, zIndex: zIndexModalinfo
			, close: function(){$(iframeSelect).html("");} // avoid the src to be called again
		});
		$(iframeSelect + " iframe").attr("src", url);
	};

	var iframePanel = function (url) {
		var that=this;
		/*
		 * try to get information about the data returned by the URL
		 */
		$.ajax({
			type: 'GET',
			async: false,
			url:url,
			complete: function (XMLHttpRequest, textStatus) {
				var fileName = url;
				var type = "undef";
				var headers = XMLHttpRequest.getAllResponseHeaders();
				var ls = headers.split(/\n/g);
				/*
				 * Extract both filename and content type from the http header
				 */
				for( var i=0 ; i<ls.length ; i++) {
					var l = ls[i];
					if( l.startsWith("Content-Disposition") ) {
						var res = /filename=(.*)/.exec(l);
						if( res ) fileName = res[1].replace(/\"/g, '');
					} else if( l.startsWith("Content-Type") ) {
						var res = /Content-Type:\s*(.*)/.exec(l);
						if( res ) type = res[1].replace(/\"/g, '');
					}
				}
				/*
				 * Open an iframe if the content type can be displayed.
				 * Let the browser do in any others cases
				 */
				for( var t=0 ; t<previewTypes.length ; t++ ) {
					if( type.match(previewTypes[t]) ) {
						that.openIframePanel(url);
						return;
					} 
				}
				Out.info("Mime type " + type + " not recognized; open a tab");
				PageLocation.changeLocation(url, fileName);
			}
		});
	};			

	var info = function (content, title) {
		Out.info("Info Popup " + content);
		buildStandardPopup("infologo", getTitle("INFO", title), formatMessage(content));
	};
	var infoObject = function (object, title) {
		Out.info("Object Info Popup ");
		buildStandardPopup("infologo", getTitle("INFO", title), '<pre>\n\n\n\n' + dump(object, '  ') + '<pre>');
	};
	var confirm = function(content, handler, title) {
		Out.info("Confirm Popup " + content);
		var hdl = (handler == undefined )? function(){alert("No attached Handler");}: handler;
		buildStandardPopup("confirmlogo",getTitle("CONFIRMATION", title), formatMessage(getTitle(content)));
		$(popupSelect).dialog( "option"
				, "buttons"
				, [ { text: "Cancel", click: function() { $( this ).dialog( "close" );} }
				, { text: "Ok"    , click: function() { $( this ).dialog( "close" ); hdl();} }]);
	};
	var error = function (content, title) {
		Out.info("Error Popup " + content);
		buildStandardPopup("warninglogo", getTitle("ERROR", title), formatMessage(content));
	};
	var getHtml = function() {
		return $(divSelect).html();
	};
	var center = function() {

		var parent = $(divSelect).parent();
		parent.css("position","absolute");
		parent.css("top", Math.max(0, (($(window).height() - parent.outerHeight()) / 2) + 
				$(window).scrollTop()) + "px");
		parent.css("left", Math.max(0, (($(window).width() - parent.outerWidth()) / 2) + 
				$(window).scrollLeft()) + "px");
	};
	var close = function() {
		$(divSelect).dialog("close");
	};

	var resize = function(){
		var heighthMax = $(window).height()*0.9;
		var widthMax = $(window).width()*0.9;
		var ctnt = $("#" + divIdCnt);
		/*
		 * Take the size of the content
		 */
		height =  ctnt.height();
		width = ctnt.width();
		/*
		 * Limit the size of the container to the maximum size allowed
		 */
		if(height > heighthMax) {
			ctnt.css("height", heighthMax);
			height = heighthMax;
		}
		if(width > widthMax) {
			ctnt.css("width", widthMax);
			width = widthMax;
		}			
		Out.debug("Resize Modal Box to " +  (8+ width) + "px x " + (8+ height) + "px");
		/*
		 * Resize it to fits the size constraints
		 * Add 8px for the borders: avoids double scroll-bars
		 */
		var smc = $("#modalinfodiv");
		smc.css('height', (8+ height)); 
		smc.css('width', (8+ width)); 
		$(window).trigger('resize.simplemodal'); 
	};

	/*
	 * exports
	 */
	var pblc = {};
	pblc.dataPanel = dataPanel;
	pblc.urlDataPanel = urlDataPanel;
	pblc.iframePanel = iframePanel;
	pblc.openIframePanel = openIframePanel;
	pblc.info    = info;
	pblc.infoObject    = infoObject;
	pblc.confirm = confirm;
	pblc.error   = error;
	pblc.getHtml   = getHtml;
	pblc.center   = center;
	pblc.close   = close;
	pblc.resize   = resize;
	return pblc;
}();

/*****************************************************************************************************
 * Object showing AJAX callback progress
 */
Processing  = function() {
	/*
	 * public functions
	 */
	var openTime = -1;	
	var jsonError = function (jsondata, msg) {
		if( jsondata == undefined || jsondata == null ) {
			Modalinfo.error("JSON ERROR: " + msg + ": no data returned" );
			return true;
		}
		else if( jsondata.errormsg != null) {
			Modalinfo.error("JSON ERROR: " + msg + ": "  + jsondata.errormsg);
			return true;
		}	
		return false;
	};
	var showAndHide = function(message){
		Out.debug("PROCESSSING (show and hide) " + message);
		show(message);
		setTimeout('$("#saadaworking").css("display", "none");$("#saadaworkingContent").css("display", "none");', 500);		
	};
	var show = function (message) {
		/*
		 * String is, duplcated because if it comes from aJSON.stringify, the content of the JSON object may be altered
		 */
		var m = message;
		m = m.replace(/"/g, '');
		Out.info("PROCESSSING " + m);
		stillToBeOpen = true;
		if( $('#saadaworking').length == 0){	
			$(document.body).append(
					'<div id="saadaworking" style="margin: auto;padding: 5px; display: none;z-index: ' + zIndexProcessing 
					+ ';opacity: 0.5;top: 0; right: 0; bottom: 0; left: 0;background-color: black;position: fixed;"></div>'
					+ '<div id="saadaworkingContent" style="position:absolute; top:50%;margin-top:-22px;'
					+ ' width: 300px;  margin-left: -150px; left: 50%; background-color: white; opacity: 1;z-index: ' 
					+ (zIndexProcessing+1) + ';'
					+ ' border:5px solid #DDD; border-radius: 5px"></div>');
		}
		$('#saadaworkingContent').html("<div class=progresslogo>" 
				+ "</div><div id=saadaworkingContentText class=help style='margin-top: 8px; display: inline; width: 240px; float:left; padding: 5px;font-size: small;'>" 
				+ m + "</div>");
		$('#saadaworking').css("display", "inline");
		$('#saadaworkingContent').css("display", "inline");
		openTime = new Date().getTime() ;
	};
	var hide = function () {
		Out.debug("close processing");
		var msg = $("#saadaworkingContentText").text();
		var seconds = new Date().getTime() ;
		/*
		 * Make sure the progress windows remains open at least 700ms: avoids blinking
		 */
		if( (seconds - openTime) < 700 ) {
			setTimeout('Processing.closeIfNoChange("' + msg + '" )', 700);
		} else    {
			$("#saadaworking").css("display", "none");$("#saadaworkingContent").css("display", "none");
		}
	};
	var closeIfNoChange = function(lastMsg){
		var currentMsg = $("#saadaworkingContentText").text();
		if( currentMsg == lastMsg) {
			$('#saadaworking').css("display", "none");
			$('#saadaworkingContent').css("display", "none");	
		} else {
			Out.debug("The content of the progress dialog has changed: not closing it");
		}
	};
	/*
	 * exports
	 */
	var pblc = {};
	pblc.show   = show;
	pblc.hide   = hide;
	pblc.closeIfNoChange   = closeIfNoChange;
	pblc.jsonError   = jsonError;
	pblc.showAndHide = showAndHide;
	return pblc;
}();


/*****************************************************************************************************
 * Console functions:
 * -Msg(): print out a message with the caller stacl position
 * -Trace(): print out a message with the caller stack trace
 */
Out = function() {
	var debugMode = false;
	var trace = false;
	var packedMode = false;
	/*
	 * Privates functions
	 */
	var printMsg = function (level, msg, withTrace) {
		if( !packedMode ){
			var e = new Error('dummy');	
			var stk;
			console.log(level + ": " + msg);
			/*
			 * IE ignore the stack property of the object Error
			 */
			if( withTrace && (stk = e.stack) != null ) {
				var ls = stk.split("\n");
				/*
				 * Always display the 4th lines of the stack
				 * The 3rd is the current line : not relevant
				 * The 4th refers to the caller
				 */
				for( var i=3 ; i<ls.length ; i++ ) {
					//if( i == 3) continue;
					console.log(ls[i]);
					if( i > 3 && ! withTrace) break;
				}
			}
		}
	};
	/*
	 * Public functions
	 */
	var setPackedMode = function() {
		debugModeOff();
		traceModeOff();
		packedMode = true;
	};
	var traceOn = function() {
		packedMode = false;
		trace = true;
	};
	var traceOff = function() {
		trace = false;
	};
	var debugModeOn = function() {
		packedMode = false;
		debugMode = true;
	};
	var debugModeOff = function() {
		debugMode = false;
	};
	var debugMsg = function (msg) {
		if( debugMode ) printMsg("DEBUG", msg, false);
	};
	var debugTrace = function (msg) {
		if( debugMode ) printMsg("DEBUG", msg, true);
	};
	var debug = function (msg) {
		if( debugMode ) printMsg("DEBUG", msg, trace);
	};
	var infoMsg = function infoMsg(msg) {
		printMsg(" INFO", msg, false);
	};
	var infoTrace  =function (msg) {
		printMsg(" INFO", msg, true);
	};
	var info  =function (msg) {
		printMsg(" INFO", msg, trace);
	};
	var setdebugModeFromUrl = function() {
		/*
		 * Set the debug mode from the debug parameters
		 */
		var debug  =  (RegExp('debug=' + '(.+?)(&|$)').exec(location.search)||[,null])[1];
		debugModeOff();
		traceOff();

		if( debug != null ) {
			if( debug == "on" ) {
				Out.info("Set debug on and trace off");
				Out.debugModeOn();
				Out.traceOff();
			} else if( debug == "withtrace" ) {
				Out.info("Set debug on and trace on");
				Out.debugModeOn();
				Out.traceOn();
			} else if( debug == "traceonly" ) {
				Out.info("Set debug off and trace on");
				Out.debugModeOff();
				Out.traceOn();
			} else {
				Modalinfo.info("debug parameter must be either on, withtrace or traceonly. It is ignored for this session.");
			}
		}
	};

	/*
	 * Exports
	 */
	var pblc = {};
	pblc.debugMsg     = debugMsg;
	pblc.debugTrace   = debugTrace;
	pblc.infoMsg      = infoMsg;
	pblc.infoTrace    = infoTrace;
	pblc.info         = info;
	pblc.debugModeOn  = debugModeOn;
	pblc.debugModeOff = debugModeOff;
	pblc.debug        = debug;
	pblc.traceOn      = traceOn;
	pblc.traceOff     = traceOff;
	pblc.setPackedMode = setPackedMode;
	pblc.setdebugModeFromUrl = setdebugModeFromUrl;
	return pblc;
}();

//Processing.show("Load scripts");
/*****************************************************************************************************
 * Download class 
 * Location had to be renamed PageLocation to avoid a conflict with AladinLite
 */
PageLocation = function () {
	var that = this;
	var downloadIframe = null;
	/*
	/*
	 * Public functions
	 */
	var changeLocation = function (url, title){
		Out.info("changeLocation to " + url);
		authOK = true;
		var t = ( title )? title: '_blank';
		window.open (url, t);
	};
	var download = function (url){
		authOK = true;
		if( !url.startsWith("http")) {
			url = window.location.protocol + "//" + window.location.hostname +  (location.port?":"+location.port:"") + window.location.pathname + "/" + url; 
		}
		Out.info("Download " + url);
		if( downloadIframe == null ) {
			$(document.body).append('<iframe id="downloadIframe" src="' + url + '" style="display: hiddden;"></iframe>');
			this.downloadIframe =  $("#downloadIframe");
		} else {
			this.downloadIframe.attr("src", url);
		}
	};
	var confirmBeforeUnlaod = function() {
		Out.info("Prompt user before to leave");
		window.onbeforeunload = function() {
			if( !that.authOK) {
				if( WebSamp_mVc.fireIsConnected() ) {
					WebSamp_mVc.fireUnregister();
				}
				return  'WARNING: Reloading or leaving this page will lost the current session';
			} else {
				that.authOK = false;
			}
		};
	};
	/*
	 * exports
	 */
	var pblc = {};
	pblc.download   = download;
	pblc.changeLocation   = changeLocation;
	pblc.confirmBeforeUnlaod   = confirmBeforeUnlaod; // oupps type
	pblc.confirmBeforeUnload   = confirmBeforeUnlaod;
	return pblc;
}();



console.log('=============== >  basics.js ');

//samp
//----
//Provides capabilities for using the SAMP Web Profile from JavaScript.
//Exported tokens are in the samp.* namespace.
//Inline documentation is somewhat patchy (partly because I don't know
//what javascript documentation is supposed to look like) - it is
//suggested to use it conjunction with the provided examples,
//currently visible at http://astrojs.github.com/sampjs/
//(gh-pages branch of github sources).
WebSamp_Mvc = function(appName, iconUrl, description) {

	/**
	 * keep a reference to ourselves
	 */
	var that = this;
	/**
	 * who is listening to us?
	 */
	var listener = null;
	/**
	 * add a listener to this view
	 */
	var addListener = function(list){
		listener = list;
	};
	/*
	 * Description of the local SAMP client
	 */
	var meta = {
			"samp.name": appName,
			"samp.description": description,
			"samp.icon.url": iconUrl
	};
	var subs=null; //subscription of SAMP events

	//=========================================================================
	// Constants defining well-known location of SAMP Web Profile hub etc.
	var WEBSAMP_PORT = 21012;
	var WEBSAMP_PATH = "/";
	var WEBSAMP_PREFIX = "samp.webhub.";
	var WEBSAMP_CLIENT_PREFIX = "";

	TYPE_STRING = "string";
	TYPE_LIST = "list";
	TYPE_MAP = "map";

	var noHub = true;

	var heir = function(proto) {
		function F() {};
		F.prototype = proto;
		return new F();
	};

	//======================================================================
	// Utility functions for navigating DOM etc.
	var getSampType = function(obj) {
		if (typeof obj === "string") {
			return TYPE_STRING;
		}
		else if (obj instanceof Array) {
			return TYPE_LIST;
		}
		else if (obj instanceof Object && obj !== null) {
			return TYPE_MAP;
		}
		else {
			throw new Error("Not legal SAMP object type: " + obj);
		}
	};
	var getChildElements = function(el, childTagName) {
		var children = el.childNodes;
		var child;
		var childEls = [];
		var i;
		for (i = 0; i < children.length; i++) {
			child = children[i];
			if (child.nodeType === 1) {  // Element
				if (childTagName && (child.tagName !== childTagName)) {
					throw new Error("Child <" + children[i].tagName + ">"
							+ " of <" + el.tagName + ">"
							+ " is not a <" + childTagName + ">");
				}
				childEls.push(child);
			}
		}
		return childEls;
	};
	var getSoleChild = function(el, childTagName) {
		var children = getChildElements(el, childTagName);
		if (children.length === 1 ) {
			return children[0];
		}
		else {
			throw new Error("No sole child of <" + el.tagName + ">");
		}
	};
	var getTextContent = function(el) {
		var txt = "";
		var i;
		var child;
		for (i = 0; i < el.childNodes.length; i++ ) {
			child = el.childNodes[i];
			if (child.nodeType === 1) {           // Element 
				throw new Error("Element found in text content");
			}
			else if (child.nodeType === 3 ||      // Text
					child.nodeType === 4 ) {     // CDATASection
				txt += child.nodeValue;
			}
		}
		return txt;
	};
	var stringify = function(obj) {
		return typeof JSON === "undefined" ? "..." : JSON.stringify(obj);
	};

	//=========================================================================
	// XmlRpc class:
	// Utilities for packing and unpacking XML-RPC messages.
	// See xml-rpc.com.
	var XmlRpc = {};

	// Takes text and turns it into something suitable for use as the content
	// of an XML-RPC string - special characters are escaped.
	XmlRpc.escapeXml = function(s) {
		return s.replace(/&/g, "&amp;")
		.replace(/</g, "&lt;")
		.replace(/>/g, "&gt;");
	};

	// Asserts that the elements of paramList match the types given by typeList.
	// TypeList must be an array containing only TYPE_STRING, TYPE_LIST
	// and TYPE_MAP objects in some combination.  paramList must be the
	// same length.
	// In case of mismatch an error is thrown.
	XmlRpc.checkParams = function(paramList, typeList) {
		var i;
		for (i = 0; i < typeList.length; i++) {
			if (typeList[i] !== TYPE_STRING &&
					typeList[i] !== TYPE_LIST &&
					typeList[i] !== TYPE_MAP) {
				throw new Error("Unknown type " + typeList[i]
				+ " in check list");
			}
		}
		var npar = paramList.length;
		var actualTypeList = [];
		var ok = true;
		for (i = 0; i < npar; i++) {
			actualTypeList.push(getSampType(paramList[i]));
		}
		ok = ok && (typeList.length === npar);
		for (i = 0; ok && i < npar; i++ ) {
			ok = ok && typeList[i] === actualTypeList[i];
		}
		if (!ok) {
			throw new Error("Param type list mismatch: " 
					+ "[" + typeList + "] != "
					+ "[" + actualTypeList + "]");
		}
	};

	// Turns a SAMP object (structure of strings, lists, maps) into an
	// XML string suitable for use with XML-RPC.
	XmlRpc.valueToXml = function v2x(obj, prefix) {
		prefix = prefix || "";
		var a;
		var i;
		var result;
		var type = getSampType(obj);
		if (type === TYPE_STRING) {
			return prefix
			+ "<value><string>"
			+ XmlRpc.escapeXml(obj)
			+ "</string></value>";
		} else if (type === TYPE_LIST) {
			result = [];
			result.push(prefix + "<value>",
					prefix + "  <array>",
					prefix + "    <data>");
			for (i = 0; i < obj.length; i++) {
				result.push(v2x(obj[i], prefix + "      "));
			}
			result.push(prefix + "    </data>",
					prefix + "  </array>",
					prefix + "</value>");

			return result.join("\n");
		} else if (type === TYPE_MAP) {
			result = [];
			result.push(prefix + "<value>");
			result.push(prefix + "  <struct>");
			for (i in obj) {
				result.push(prefix + "    <member>");
				result.push(prefix + "      <name>"
						+ XmlRpc.escapeXml(i)
						+ "</name>");
				result.push(v2x(obj[i], prefix + "      "));
				result.push(prefix + "    </member>");
			}
			result.push(prefix + "  </struct>");
			result.push(prefix + "</value>");
			return result.join("\n");
		} else {
			throw new Error("bad type");  // shouldn't get here
		}
	};

	// Turns an XML string from and XML-RPC message into a SAMP object
	// (structure of strings, lists, maps).
	XmlRpc.xmlToValue = function x2v(valueEl, allowInt) {
		var childEls = getChildElements(valueEl);
		var i;
		var j;
		var txt;
		var node;
		var childEl;
		var elName;
		if (childEls.length === 0) {
			return getTextContent(valueEl);
		}  else if (childEls.length === 1) {
			childEl = childEls[0];
			elName = childEl.tagName;
			if (elName === "string") {
				return getTextContent(childEl);
			} else if (elName === "array") {
				var valueEls =
					getChildElements(getSoleChild(childEl, "data"), "value");
				var list = [];
				for (i = 0; i < valueEls.length; i++) {
					list.push(x2v(valueEls[i], allowInt));
				}
				return list;
			}
			else if (elName === "struct") {
				var memberEls = getChildElements(childEl, "member");
				var map = {};
				var s_name;
				var s_value;
				var jc;
				for (i = 0; i < memberEls.length; i++) {
					s_name = undefined;
					s_value = undefined;
					for (j = 0; j < memberEls[i].childNodes.length; j++) {
						jc = memberEls[i].childNodes[j];
						if (jc.nodeType == 1) {
							if (jc.tagName === "name") {
								s_name = getTextContent(jc);
							} else if (jc.tagName === "value") {
								s_value = x2v(jc, allowInt);
							}
						}
					}
					if (s_name !== undefined && s_value !== undefined) {
						map[s_name] = s_value;
					} else {
						throw new Error("No <name> and/or <value> "
								+ "in <member>?");
					}
				}
				return map;
			} else if (allowInt && (elName === "int" || elName === "i4")) {
				return getTextContent(childEl);
			} else {
				throw new Error("Non SAMP-friendly value content: "
						+ "<" + elName + ">");
			}
		}
		else {
			throw new Error("Bad XML-RPC <value> content - multiple elements");
		}
	};

	// Turns the content of an XML-RPC <params> element into an array of
	// SAMP objects.
	XmlRpc.decodeParams = function(paramsEl) {
		var paramEls = getChildElements(paramsEl, "param");
		var i;
		var results = [];
		for (i = 0; i < paramEls.length; i++) {
			results.push(XmlRpc.xmlToValue(getSoleChild(paramEls[i], "value")));
		}
		return results;
	};

	// Turns the content of an XML-RPC <fault> element into an XmlRpc.Fault
	// object.
	XmlRpc.decodeFault = function(faultEl) {
		var faultObj = XmlRpc.xmlToValue(getSoleChild(faultEl, "value"), true);
		return new XmlRpc.Fault(faultObj.faultString, faultObj.faultCode);
	};

	// Turns an XML-RPC response element (should be <methodResponse>) into
	// either a SAMP response object or an XmlRpc.Fault object.
	// Note that a fault response does not throw an error, so check for
	// the type of the result if you want to know whether a fault occurred.
	// An error will however be thrown if the supplied XML does not
	// correspond to a legal XML-RPC response.
	XmlRpc.decodeResponse = function(xml) {
		var mrEl = xml.documentElement;
		if (mrEl.tagName !== "methodResponse") {
			throw new Error("Response element is not <methodResponse>");
		}
		var contentEl = getSoleChild(mrEl);
		if (contentEl.tagName === "fault") {
			return XmlRpc.decodeFault(contentEl);
		} else if (contentEl.tagName === "params") {
			return XmlRpc.decodeParams(contentEl)[0];
		} else {
			throw new Error("Bad XML-RPC response - unknown element"
					+ " <" + contentEl.tagName + ">");
		}
	};

	// XmlRpc.Fault class:
	// Represents an XML-RPC Fault response.
	XmlRpc.Fault = function(faultString, faultCode) {
		this.faultString = faultString;
		this.faultCode = faultCode;
	};
	XmlRpc.Fault.prototype.toString = function() {
		return "XML-RPC Fault (" + this.faultCode + "): " + this.faultString;
	};



	//=========================================================================
	// XmlRpcRequest class:
	// Represents an call which can be sent to an XML-RPC server.
	var XmlRpcRequest = function(methodName, params) {
		this.methodName = methodName;
		this.params = params || [];
	}
	XmlRpcRequest.prototype.toString = function() {
		return this.methodName + "(" + stringify(this.params) + ")";
	};
	XmlRpcRequest.prototype.addParam = function(param) {
		this.params.push(param);
		return this;
	};
	XmlRpcRequest.prototype.addParams = function(params) {
		var i;
		for (i = 0; i < params.length; i++) {
			this.params.push(params[i]);
		}
		return this;
	};
	XmlRpcRequest.prototype.checkParams = function(typeList) {
		XmlRpc.checkParams(this.params, typeList);
	};
	XmlRpcRequest.prototype.toXml = function() {
		var lines = [];
		lines.push(
				"<?xml version='1.0'?>",
				"<methodCall>",
				"  <methodName>" + this.methodName + "</methodName>",
		"  <params>");
		for (var i = 0; i < this.params.length; i++) {
			lines.push("    <param>",
					XmlRpc.valueToXml(this.params[i], "      "),
			"    </param>");
		}
		lines.push(
				"  </params>",
		"</methodCall>");		
//		alert(lines.join("\n"));
//		alert(JSON.stringify(meta));

		return lines.join("\n");
	};


	//=========================================================================
	// XmlRpcClient class:
	// Object capable of sending XML-RPC calls to an XML-RPC server.
	// That server will typically reside on the host on which the
	// javascript is running; it is not likely to reside on the host
	// which served the javascript.  That means that sandboxing restrictions
	// will be in effect.  Much of the work done here is therefore to
	// do the client-side work required to potentially escape the sandbox.
	// The endpoint parameter, if supplied, is the URL of the XML-RPC server.
	// If absent, the default SAMP Web Profile server is used.
	var XmlRpcClient = function(endpoint) {
		this.endpoint = endpoint ||
		"http://localhost:" + WEBSAMP_PORT + WEBSAMP_PATH;
	};

	// Creates an XHR facade - an object that presents an interface
	// resembling that of an XMLHttpRequest Level 2.
	// This facade may be based on an actual XMLHttpRequest Level 2 object
	// (on browsers that support it), or it may fake one using other
	// available technology.
	//
	// The created facade in any case presents the following interface:
	//
	//    open(method, url)
	//    send(body)
	//    abort()
	//    setContentType()
	//    responseText
	//    responseXML
	//    onload
	//    onerror(err)  - includes timeout; abort is ignored
	//
	// See the documentation at http://www.w3.org/TR/XMLHttpRequest/
	// for semantics.
	//
	// XMLHttpRequest Level 2 supports Cross-Origin Resource Sharing (CORS)
	// which makes sandbox evasion possible.  Faked XHRL2s returned by
	// this method may use CORS or some other technology to evade the
	// sandbox.  The SAMP hub itself may selectively allow some of these
	// technologies and not others, according to configuration.
	XmlRpcClient.createXHR = function() {

		// Creates an XHR facade based on a genuine XMLHttpRequest Level 2.
		var XhrL2 = function(xhr) {
			this.xhr = xhr;
			xhr.onreadystatechange = (function(l2) {
				return function() {
					if (xhr.readyState !== 4) {
						return;
					} else if (!l2.completed) {
						if (+xhr.status === 200) {
							l2.completed = true;
							l2.responseText = xhr.responseText;
							l2.responseXML = xhr.responseXML;
							if (l2.onload) {
								l2.onload();
							}
						}
					}
				};
			})(this);
			xhr.onerror = (function(l2) {
				return function(event) {
					notifyHubError("XmlRpcClient.create  xhr.onerror No hub?");
					if (!l2.completed) {
						l2.completed = true;
						if (l2.onerror) {
							if (event) {
								event.toString = function() {return "No hub?";};
							} else {
								event = "No hub?";
							}
							l2.onerror(event);

						}
						/*
						 * Added by myself to process a sudden disappearance of the hub (kill -9)
						 */
					} else {
						if (l2.onerror) {
							if (event) {
								event.toString = function() {return "No hub?";};
							}
							else {
								event = "No hub?";
							}
							l2.onerror(event);
						}
					}
				};
			})(this);
			xhr.ontimeout = (function(l2) {
				return function(event) {
					if (!l2.completed) {
						l2.completed = true;
						if (l2.onerror) {
							l2.onerror("timeout");
						}
					}
				};
			})(this);
		};
		XhrL2.prototype.open = function(method, url) {
			this.xhr.open(method, url);
		};
		XhrL2.prototype.send = function(body) {
			this.xhr.send(body);
		};
		XhrL2.prototype.abort = function() {
			this.xhr.abort();
		}
		XhrL2.prototype.setContentType = function(mimeType) {
			if ("setRequestHeader" in this.xhr) {
				this.xhr.setRequestHeader("Content-Type", mimeType);
			}
		}

		// Creates an XHR facade based on an XDomainRequest (IE8+ only).
		var XdrL2 = function(xdr) {
			this.xdr = xdr;
			xdr.onload = (function(l2) {
				return function() {
					var e;
					l2.responseText = xdr.responseText;
					if (xdr.contentType === "text/xml" ||
							xdr.contentType === "application/xml" ||
							/\/x-/.test(xdr.contentType)) {
						try {
							var xdoc = new ActiveXObject("Microsoft.XMLDOM");
							xdoc.loadXML(xdr.responseText);
							l2.responseXML = xdoc;
						}
						catch (e) {
							l2.responseXML = e;
						}
					}
					if (l2.onload) {
						l2.onload();
					}
				};
			})(this);
			xdr.onerror = (function(l2) {
				return function(event) {
					if (l2.onerror) {
						l2.onerror(event);
					}
				};
			})(this);
			xdr.ontimeout = (function(l2) {
				return function(event) {
					if (l2.onerror) {
						l2.onerror(event);
					}
				};
			})(this);
		};
		XdrL2.prototype.open = function(method, url) {
			this.xdr.open(method, url);
		};
		XdrL2.prototype.send = function(body) {
			this.xdr.send(body);
		};
		XdrL2.prototype.abort = function() {
			this.xdr.abort();
		};
		XdrL2.prototype.setContentType = function(mimeType) {
			// can't do it.
		};

		// Creates an XHR Facade based on available XMLHttpRequest-type
		// capabilibities.
		// If an actual XMLHttpRequest Level 2 is available, use that.
		if (typeof XMLHttpRequest !== "undefined") {
			var xhr = new XMLHttpRequest();
			if ("withCredentials" in xhr) {
				return new XhrL2(xhr);
			}
		}

		// Else if an XDomainRequest is available, use that.
		if (typeof XDomainRequest !== "undefined") {
			return new XdrL2(new XDomainRequest());
		}

		// Else fake an XMLHttpRequest using Flash/flXHR, if available
		// and use that.
		if (typeof flensed.flXHR !== "undefined") {
			return new XhrL2(new flensed.flXHR({instancePooling: true}));
		}

		// No luck.
		throw new Error("no cross-origin mechanism available");
	};

	// Executes a request by passing it to the XML-RPC server.
	// On success, the result is passed to the resultHandler.
	// On failure, the errHandler is called with one of two possible
	// arguments: an XmlRpc.Fault object, or an Error object.
	XmlRpcClient.prototype.execute = function(req, resultHandler, errHandler) {
		(function(xClient) {
			var xhr;
			var e;
			try {
				xhr = XmlRpcClient.createXHR();
				xhr.open("POST", xClient.endpoint);
				xhr.setContentType("text/xml");
			}
			catch (e) {
				errHandler(e);
				throw e;
			}
			xhr.onload = function() {
				var xml = xhr.responseXML;
				var result;
				var e;
				if (xml) {
					try {
						result = XmlRpc.decodeResponse(xml);
					}
					catch (e) {
						notifyHubError("XmlRpcClient.prototype.execute wrong XML response");
						if (errHandler) {
							errHandler(e);
						}
						return;
					}
				} else {
					notifyHubError("XmlRpcClient.prototype.execute no XML response");
					if (errHandler) {
						errHandler("no XML response");
					}
					return;
				}
				if (result instanceof XmlRpc.Fault) {
					notifyHubError("XmlRpcClient.prototype.execute  XmlRpc.Fault");
					if (errHandler) {
						errHandler(result);
					}
				} else {
					noHub = false;
					Out.debug("noHub = false XmlRpcClient.prototype.execute");
					if (resultHandler) {
						resultHandler(result);
					}
				}
			};
			xhr.onerror = function(event) {
				if (event) {
					event.toString = function() {return "No hub?";}
				} else {
					event = "No hub";
				}
				if (errHandler) {
					errHandler(event);
				}
			};
			xhr.send(req.toXml());
			return xhr;
		})(this);
	}; 

	//=========================================================================
	// Message class:
	// Aggregates an MType string and a params map.
	var Message = function(mtype, params) {
		this["samp.mtype"] = mtype;
		this["samp.params"] = params;
	};


	//=========================================================================
	// Connection class:
	// this is what clients use to communicate with the hub.
	//
	// All the methods from the Hub Abstract API as described in the
	// SAMP standard are available as methods of a Connection object.
	// The initial private-key argument required by the Web Profile is
	// handled internally by this object - you do not need to supply it
	// when calling one of the methods.
	//
	// All these calls have the same form:
	//
	//    connection.method([method-args], resultHandler, errorHandler)
	//
	// the first argument is an array of the arguments (as per the SAMP
	// abstract hub API), the second argument is a function which is
	// called on successful completion with the result of the SAMP call
	// as its argument, and the third argument is a function which is
	// called on unsuccessful completion with an error object as its
	// argument.  The resultHandler and errorHandler arguments are optional.
	//
	// So for instance if you have a Connection object conn,
	// you can send a notify message to all other clients by doing, e.g.:
	//
	//    conn.notifyAll([new samp.Message(mtype, params)])
	//
	// Connection has other methods as well as the hub API ones
	// as documented below.
	var Connection = function(regInfo) {
		this.regInfo = regInfo;
		this.privateKey = regInfo["samp.private-key"];
		if (! typeof(this.privateKey) === "string") {
			throw new Error("Bad registration object");
		}
		this.xClient = new XmlRpcClient();
	};
	(function() {
		var connMethods = {
				call: [TYPE_STRING, TYPE_STRING, TYPE_MAP],
				callAll: [TYPE_STRING, TYPE_MAP],
				callAndWait: [TYPE_STRING, TYPE_MAP, TYPE_STRING],
				declareMetadata: [TYPE_MAP],
				declareSubscriptions: [TYPE_MAP],
				getMetadata: [TYPE_STRING],
				getRegisteredClients: [],
				getSubscribedClients: [TYPE_STRING],
				getSubscriptions: [TYPE_STRING],
				notify: [TYPE_STRING, TYPE_MAP],
				notifyAll: [TYPE_MAP],
				ping: [],
				reply: [TYPE_STRING, TYPE_MAP]
		};
		var fn;
		var types;
		for (fn in connMethods) {
			(function(fname, types) {
				// errHandler may be passed an XmlRpc.Fault or a thrown Error.
				Connection.prototype[fname] =
					function(sampArgs, resultHandler, errHandler) {
					var closer =
						(function(c) {return function() {c.close()}})(this);
					errHandler = errHandler || closer
					XmlRpc.checkParams(sampArgs, types);
					var request = new XmlRpcRequest(WEBSAMP_PREFIX + fname);
					request.addParam(this.privateKey);
					request.addParams(sampArgs);
					return this.xClient.
					execute(request, resultHandler, errHandler);
				};
			})(fn, connMethods[fn]);
		}
	})();
	Connection.prototype.unregister = function() {
		var e;
		if (this.callbackRequest) {
			try {
				this.callbackRequest.abort();
			} catch (e) {
			}
		}
		var request = new XmlRpcRequest(WEBSAMP_PREFIX + "unregister");
		request.addParam(this.privateKey);
		try {
			this.xClient.execute(request);
		} catch (e) {
			// log unregister failed
		}
		delete this.regInfo;
		delete this.privateKey;
	};

	// Closes this connection.  It unregisters from the hub if still
	// registered, but may harmlessly be called multiple times.
	Connection.prototype.close = function() {
		var e;
		if (this.closed) {
			return;
		}
		this.closed = true;
		try {
			if (this.regInfo) {
				this.unregister();
			}
		} catch (e) {
		}
		if (this.onclose) {
			oc = this.onclose;
			delete this.onclose;
			try {
				oc();
			} catch (e) {
			}
		}
	};

	// Arranges for this connection to receive callbacks.
	//
	// The callableClient argument must be an object implementing the
	// SAMP callable client API, i.e. it must have the following methods:
	//
	//     receiveNotification(string sender-id, map message)
	//     receiveCall(string sender-id, string msg-id, map message)
	//     receiveResponse(string responder-id, string msg-tag, map response)
	// 
	// The successHandler argument will be called with no arguments if the
	// allowCallbacks hub method completes successfully - it is a suitable
	// hook to use for declaring subscriptions.
	//
	// The CallableClient class provides a suitable implementation, see below.
	Connection.prototype.setCallable = function(callableClient,
			successHandler) {
		var e;
		if (this.callbackRequest) {
			try {
				this.callbackRequest.abort();
			} catch (e) {
			} finally {
				delete this.callbackRequest;
			}
		}
		if (!callableClient && !this.regInfo) {
			return;
		}
		var request =
			new XmlRpcRequest(WEBSAMP_PREFIX + "allowReverseCallbacks");
		request.addParam(this.privateKey);
		request.addParam(callableClient ? "1" : "0");
		var closer = (function(c) {return function() {c.close()}})(this);
		if (callableClient) {
			(function(connection) {
				var invokeCallback = function(callback) {
					var methodName = callback["samp.methodName"];
					var methodParams = callback["samp.params"];
					var handlerFunc = undefined;
					if (methodName === WEBSAMP_CLIENT_PREFIX
							+ "receiveNotification") {
						handlerFunc = callableClient.receiveNotification;
					} else if (methodName === WEBSAMP_CLIENT_PREFIX
							+ "receiveCall") {
						handlerFunc = callableClient.receiveCall;
					} else if (methodName === WEBSAMP_CLIENT_PREFIX
							+ "receiveResponse") {
						handlerFunc = callableClient.receiveResponse;
					} else {
						// unknown callback??
					}
					if (handlerFunc) {
						handlerFunc.apply(callableClient, methodParams);
					}
				};
				var startTime;
				var resultHandler = function(result) {
					if (getSampType(result) != TYPE_LIST) {
						errHandler(new Error("pullCallbacks result not List"));
						return;
					}
					var i;
					var e;
					for (i = 0; i < result.length; i++) {
						try {
							invokeCallback(result[i]);
						} catch (e) {
							// log here?
						}
					}
					callWaiter();
				};
				var errHandler = function(error) {
					var elapsed = new Date().getTime() - startTime;
					if (elapsed < 1000) {
						connection.close();
					} else {
						// probably a timeout
						callWaiter();
					}
				};
				var callWaiter = function() {
					if (!connection.regInfo) {
						return;
					}
					var request =
						new XmlRpcRequest(WEBSAMP_PREFIX + "pullCallbacks");
					request.addParam(connection.privateKey);
					request.addParam("600");
					startTime = new Date().getTime();
					connection.callbackRequest =
						connection.xClient.
						execute(request, resultHandler, errHandler);
				};
				var sHandler = function() {
					callWaiter();
					successHandler();
				};
				connection.xClient.execute(request, sHandler, closer);
			})(this);
		}
		else {
			this.xClient.execute(request, successHandler, closer);
		}
	};

	// Takes a public URL and returns a URL that can be used from within
	// this javascript context.  Some translation may be required, since
	// a URL sent by an external application may be cross-domain, in which
	// case browser sandboxing would typically disallow access to it.
	Connection.prototype.translateUrl = function(url) {
		var translator = this.regInfo["samp.url-translator"] || "";
		return translator + url;
	};
	Connection.Action = function(actName, actArgs, resultKey) {
		this.actName = actName;
		this.actArgs = actArgs;
		this.resultKey = resultKey;
	};

	//================================================================================    
	// Suitable implementation for a callable client object which can
	// be supplied to Connection.setCallable().
	// Its callHandler and replyHandler members are string->function maps
	// which can be used to provide handler functions for MTypes and
	// message tags respectively.
	//
	// In more detail:
	// The callHandler member maps a string representing an MType to
	// a function with arguments (senderId, message, isCall).
	// The replyHandler member maps a string representing a message tag to
	// a function with arguments (responderId, msgTag, response).
	var CallableClient = function(connection) {
		this.callHandler = {};
		this.replyHandler = {};
	};
	CallableClient.prototype.init = function(connection) {
	};
	CallableClient.prototype.receiveNotification = function(senderId, message) {
		var mtype = message["samp.mtype"];
		var handled = false;
		var e;
		if (mtype in this.callHandler) {
			try {
				this.callHandler[mtype](senderId, message, false);
			} catch (e) {
			}
			handled = true;
		}
		return handled;
	};
	CallableClient.prototype.receiveCall = function(senderId, msgId, message) {
		var mtype = message["samp.mtype"];
		var handled = false;
		var response;
		var result;
		var e;
		if (mtype in this.callHandler) {
			try {
				result = this.callHandler[mtype](senderId, message, true) || {};
				response = {"samp.status": "samp.ok",
						"samp.result": result};
				handled = true;
			} catch (e) {
				response = {"samp.status": "samp.error",
						"samp.error": {"samp.errortxt": e.toString()}};
			}
		} else {
			response = {"samp.status": "samp.warning",
					"samp.result": {},
					"samp.error": {"samp.errortxt": "no action"}};
		}
		this.connection.reply([msgId, response]);
		return handled;
	};
	CallableClient.prototype.receiveResponse = function(responderId, msgTag,
			response) {
		var handled = false;
		var e;
		if (msgTag in this.replyHandler) {
			try {
				this.replyHandler[msgTag](responderId, msgTag, response);
				handled = true;
			} catch (e) {
			}
		}
		return handled;
	};
	CallableClient.prototype.calculateSubscriptions = function() {
		var subs = {};
		var mt;
		for (mt in this.callHandler) {
			subs[mt] = {};
		}
		return subs;
	};


	//================================================================================
	// ClientTracker is a CallableClient which also provides tracking of
	// registered clients.
	//
	// Its onchange member, if defined, will be called with arguments
	// (client-id, change-type, associated-data) whenever the list or
	// characteristics of registered clients has changed.
	var ClientTracker = function() {
		var tracker = this;
		this.ids = {};
		this.metas = {};
		this.subs = {};
		this.replyHandler = {};
		this.callHandler = {
				"samp.hub.event.shutdown": function(senderId, message) {
					tracker.connection.close();
				},
				"samp.hub.disconnect": function(senderId, message) {
					tracker.connection.close();
				},
				"samp.hub.event.register": function(senderId, message) {
					var id = message["samp.params"]["id"];
					tracker.ids[id] = true;
					tracker.changed(id, "register", null);
				},
				"samp.hub.event.unregister": function(senderId, message) {
					var id = message["samp.params"]["id"];
					delete tracker.ids[id];
					delete tracker.metas[id];
					delete tracker.subs[id];
					tracker.changed(id, "unregister", null);
				},
				"samp.hub.event.metadata": function(senderId, message) {
					var id = message["samp.params"]["id"];
					var meta = message["samp.params"]["metadata"];
					tracker.metas[id] = meta;
					tracker.changed(id, "meta", meta);
				},
				"samp.hub.event.subscriptions": function(senderId, message) {
					var id = message["samp.params"]["id"];
					var subs = message["samp.params"]["subscriptions"];
					tracker.subs[id] = subs;
					tracker.changed(id, "subs", subs);
				}
		};
	};
	ClientTracker.prototype = heir(CallableClient.prototype);
	ClientTracker.prototype.changed = function(id, type, data) {
		if (this.onchange) {
			this.onchange(id, type, data);
		}
	};
	ClientTracker.prototype.init = function(connection) {
		var tracker = this;
		this.connection = connection;
		var retrieveInfo = function(id, type, infoFuncName, infoArray) {
			connection[infoFuncName]([id], function(info) {
				infoArray[id] = info;
				tracker.changed(id, type, info);
			});
		};
		connection.getRegisteredClients([], function(idlist) {
			var i;
			var id;
			tracker.ids = {};
			for (i = 0; i < idlist.length; i++) {
				id = idlist[i];
				tracker.ids[id] = true;
				retrieveInfo(id, "meta", "getMetadata", tracker.metas);
				retrieveInfo(id, "subs", "getSubscriptions", tracker.subs);
			}
			tracker.changed(null, "ids", null);
		});
	};
	ClientTracker.prototype.getName = function(id) {
		var meta = this.metas[id];
		return (meta && meta["samp.name"]) ? meta["samp.name"] : "[" + id + "]";
	};


	//==========================================================================================
	// Connector class:
	// A higher level class which can manage transparent hub
	// registration/unregistration and client tracking.
	//
	// On construction, the name argument is mandatory, and corresponds
	// to the samp.name item submitted at registration time.
	// The other arguments are optional.
	// meta is a metadata map (if absent, no metadata is declared)
	// callableClient is a callable client object for receiving callbacks
	// (if absent, the client is not callable).
	// subs is a subscriptions map (if absent, no subscriptions are declared)
	var Connector = function(name, meta, callableClient, subs) {
		this.name = name;
		this.meta = meta;
		this.callableClient = callableClient;
		this.subs = subs;
		this.regTextNodes = [];
		this.whenRegs = [];
		this.whenUnregs = [];
		this.connection = undefined;
		this.onreg = undefined;
		this.onunreg = undefined;
	};
	var setRegText = function(connector, txt) {
		Out.info(txt);
		var i;
		var nodes = connector.regTextNodes;
		var node;
		for (i = 0; i < nodes.length; i++) {
			node = nodes[i];
			node.innerHTML = "";
			node.appendChild(document.createTextNode(txt));
		}
	};
	Connector.prototype.setConnection = function(conn) {
		var connector = this;
		var e;
		if (this.connection) {
			this.connection.close();
			if (this.onunreg) {
				try {
					this.onunreg();
				} catch (e) {
				}
			}
		}
		this.connection = conn;
		if (conn) {
			conn.onclose = function() {
				connector.connection = null;
				if (connector.onunreg) {
					try {
						connector.onunreg();
					} catch (e) {
					}
				}
				connector.update();
			};
			if (this.meta) {
				conn.declareMetadata([this.meta]);
			}
			if (this.callableClient) {
				if (this.callableClient.init) {
					this.callableClient.init(conn);
				}
				conn.setCallable(this.callableClient, function() {
					conn.declareSubscriptions([connector.subs]);
				});
			}
			if (this.onreg) {
				try {
					this.onreg(conn);
				} catch (e) {
				}
			}
		}
		this.update();
	};
	Connector.prototype.register = function() {
		var connector = this;
		var regErrHandler = function(err) {
			setRegText(connector, "no (" + err.toString() + ")");
			notifyHubError("Connector: no (" + err.toString() + ")");
		};
		var regSuccessHandler = function(conn) {
			connector.setConnection(conn);
			setRegText(connector, conn ? "Yes" : "No");
			Out.debug("noHub = false Connector.prototype.register");
			noHub = false;
		};
		register(this.name, regSuccessHandler, regErrHandler);
	};
	Connector.prototype.unregister = function() {
		if (this.connection) {
			this.connection.unregister([]);
			this.setConnection(null);
		}
	};

	// Returns a document fragment which contains Register/Unregister
	// buttons for use by the user to attempt to connect/disconnect
	// with the hub.  This is useful for models where explicit
	// user registration is encouraged or required, but when using
	// the register-on-demand model such buttons are not necessary.
	Connector.prototype.createRegButtons = function() {
		var connector = this;
		var regButt = document.createElement("button");
		regButt.setAttribute("type", "button");
		regButt.appendChild(document.createTextNode("Register"));
		regButt.onclick = function() {connector.register();};
		this.whenUnregs.push(regButt);
		var unregButt = document.createElement("button");
		unregButt.setAttribute("type", "button");
		unregButt.appendChild(document.createTextNode("Unregister"));
		unregButt.onclick = function() {connector.unregister();};
		this.whenRegs.push(unregButt);
		var regText = document.createElement("span");
		this.regTextNodes.push(regText);
		var node = document.createDocumentFragment();
		node.appendChild(regButt);
		node.appendChild(document.createTextNode(" "));
		node.appendChild(unregButt);
		var label = document.createElement("span");
		label.innerHTML = " <strong>Registered: </strong>";
		node.appendChild(label);
		node.appendChild(regText);
		this.update();
		return node;
	};

	Connector.prototype.update = function() {
		var i;
		var isConnected = !! this.connection;
		var enableds = isConnected ? this.whenRegs : this.whenUnregs;
		var disableds = isConnected ? this.whenUnregs : this.whenRegs;
		for (i = 0; i < enableds.length; i++) {
			enableds[i].removeAttribute("disabled");
		}
		for (i = 0; i < disableds.length; i++) {
			disableds[i].setAttribute("disabled", "disabled");
		}
		setRegText(this, "No");
	};

	// Provides execution of a SAMP operation with register-on-demand.
	// You can use this method to provide lightweight registration/use
	// of web SAMP.  Simply provide a connHandler function which
	// does something with a connection (e.g. sends a message) and
	// Connector.runWithConnection on it.  This will connect if not
	// already connected, and call the connHandler on with the connection.
	// No explicit registration action is then required from the user.
	//
	// If the regErrorHandler argument is supplied, it is a function of
	// one (error) argument called in the case that registration-on-demand
	// fails.
	//
	// This is a more-or-less complete sampjs page:
	//   <script>
	//     var connector = new samp.Connector("pinger", {"samp.name": "Pinger"})
	//     var pingFunc = function(connection) {
	//       connection.notifyAll([new samp.Message("samp.app.ping", {})])
	//     }
	//   </script>
	//   <button onclick="connector.runWithConnection(pingFunc)">Ping</button>
	Connector.prototype.runWithConnection =
		function(connHandler, regErrorHandler) {
		var connector = this;
		var regSuccessHandler = function(conn) {
			connector.setConnection(conn);
			connHandler(conn);
		};
		var regFailureHandler = function(e) {
			connector.setConnection(undefined);
			regErrorHandler(e);
		};
		var pingResultHandler = function(result) {
			connHandler(connector.connection);
		};
		var pingErrorHandler = function(err) {
			register(this.name, regSuccessHandler, regFailureHandler);
		};
		if (this.connection) {
			// Use getRegisteredClients as the most lightweight check
			// I can think of that this connection is still OK.
			// Ping doesn't work because the server replies even if the
			// private-key is incorrect/invalid.  Is that a bug or not?
			this.connection.
			getRegisteredClients([], pingResultHandler, pingErrorHandler);
		} else {
			register(this.name, regSuccessHandler, regFailureHandler);
		}
	};

	// Sets up an interval timer to run at intervals and notify a callback
	// about whether a hub is currently running.
	// Every millis milliseconds, the supplied availHandler function is
	// called with a boolean argument: true if a (web profile) hub is
	// running, false if not.
	// Returns the interval timer (can be passed to clearInterval()).
	Connector.prototype.onHubAvailability = function(availHandler, millis) {
		samp.ping(availHandler);

		// Could use the W3C Page Visibility API to avoid making these
		// checks when the page is not visible.
		return setInterval(function() {samp.ping(availHandler);}, millis);
	};



	//======================================================================================
	// Misc functions
	// Determines whether a given subscriptions map indicates subscription
	// to a given mtype.
	var isSubscribed = function(subs, mtype) {
		var matching = function(pattern, mtype) {
			if (pattern == mtype) {
				return true;
			}
			else if (pattern === "*") {
				return true;
			}
			else {
				var prefix;
				var split = /^(.*)\.\*$/.exec(pat);
				if (split) {
					prefix = split[1];
					if (prefix === mtype.substring(0, prefix.length)) {
						return true;
					}
				}
			}
			return false;
		};
		var pat;
		for (pat in subs) {
			if (matching(pat, mtype)) {
				return true;
			}
		}
		return false;
	}

	// Attempts registration with a SAMP hub.
	// On success the supplied connectionHandler function is called
	// with the connection as an argument, on failure the supplied
	// errorHandler is called with an argument that may be an Error
	// or an XmlRpc.Fault.
	var register = function(appName, connectionHandler, errorHandler) {
		var xClient = new XmlRpcClient();
		var regRequest = new XmlRpcRequest(WEBSAMP_PREFIX + "register");
		var securityInfo = {"samp.name": appName};
		regRequest.addParam(securityInfo);
		regRequest.checkParams([TYPE_MAP]);
		/*
		 * We consider first the the Hub is running. That avoid multiple 
		 * connection attemps while user aggreement popup windos is open
		 */
		noHub = false;

		var resultHandler = function(result) {
			var conn;
			var e;
			try {
				conn = new Connection(result, 1000);
			}
			catch (e) {
				errorHandler(e);
				return;
			}
			connectionHandler(conn);
		};
		xClient.execute(regRequest, resultHandler, errorHandler);
	};

	// Calls the hub ping method once.  It is not necessary to be
	// registered to do this.
	// The supplied pingHandler function is called with a boolean argument:
	// true if a (web profile) hub is running, false if not.
	var ping = function(pingHandler) {
		var xClient = new XmlRpcClient();
		var pingRequest = new XmlRpcRequest(WEBSAMP_PREFIX + "ping");
		var resultHandler = function(result) {
			pingHandler(true);
		};
		var errorHandler = function(error) {
			pingHandler(false);
		};
		xClient.execute(pingRequest, resultHandler, errorHandler);
	};

	// =====================================================================================================================================


	/******** imoprte de essai.html, a nettoyer ***********/

	var tracker = new ClientTracker();
	var callHandler = tracker.callHandler;
	callHandler["samp.app.ping"] = function(senderId, message, isCall) {
		if (isCall) {
			return {text: "ping to you, " + tracker.getName(senderId)};
		}
	};
	tracker.onchange = function(id, type, data) {
		notifyTrackerReply(id, type, data);
	};



	subs = tracker.calculateSubscriptions();
	//subs = {"*": {}};

	logCc = {
			receiveNotification: function(senderId, message) {
				var handled = tracker.receiveNotification(senderId, message);
				logCallback("notification: " + message["samp.mtype"] +
						" from " + tracker.getName(senderId), handled);
			},
			receiveCall: function(senderId, msgId, message) {
				var handled = tracker.receiveCall(senderId, msgId, message);
				logCallback("call: " + message["samp.mtype"] +
						" from " + tracker.getName(senderId), handled);
			},
			receiveResponse: function(responderId, msgTag, response) {
				var handled = tracker.receiveResponse(responderId, msgTag, response);
				logCallback("response: " + msgTag +
						" from " + tracker.getName(responderId), handled);
			},
			init: function(connection) {
				tracker.init(connection);
//				connector.connection.call([id, tag, msg], null, doneFunc);
//				var createTag = (function() {
//				var count = 0;
//				return function() {
//				return "t-" + ++count;
//				};
//				})();

			}
	};

	var connector = new Connector(meta["samp.name"], meta, logCc, subs);
	connector.onunreg = function() {
		document.getElementById("clientList").innerHTML = "";
	};

	function sendMessageToClient(target, message) {
		if( target == null || target.match(/any/i) ) {
			connector.connection.notifyAll([message]);
		} else {
			connector.connection.notify([target, message]);
		}
	}
	// Controler notification
	var notifyTrackerReply = function(id, type, data) {
		listener.controlTrackerReply(id, type, data);
	};
	var notifyHubError = function(message) {
		noHub = true;
		Out.debug("notifyHubError :" + message);
		listener.controlHubError(message);
	};

	/* Exports. */
	var jss = {};
	jss.XmlRpcRequest = XmlRpcRequest;
	jss.XmlRpcClient = XmlRpcClient;
	jss.Message = Message;
	jss.TYPE_STRING = TYPE_STRING;
	jss.TYPE_LIST = TYPE_LIST;
	jss.TYPE_MAP = TYPE_MAP;
	jss.connector = connector;
	jss.isSubscribed = isSubscribed;
	jss.Connector = Connector;
	jss.CallableClient = CallableClient;
	jss.ClientTracker = ClientTracker;

	jss.addListener = addListener;
	jss.registerToHub = function() {
		if( !connector.connection )  {
			if( noHub ) connector.register();
		} else {
			Out.info("Attempt to connect, but already connected");
		}
		return {HubRunning: !noHub};    	
	};
	jss.unregisterToHub    = function() {connector.unregister();};
	jss.isConnected        = function() {return ((connector.connection)? true: false);};
	jss.notifyTrackerReply = notifyTrackerReply;
	jss.notifyHubError = notifyHubError;
	jss.sendMessageToClient= sendMessageToClient;

	return jss;
};

console.log('=============== >  WebSamp_m.js ');


WebSamp_mVc = function() {
	var that = this;
	/**
	 * who is listening to us?
	 */
	var listener = null;
	/**
	 * add a listener to this view
	 */
	var addListener = function(list) {
		listener = list;
	};
	/*
	 * used to transform local URLs in full URLs
	 */
	//if (typeof rootUrl != 'undefined') 
	var rootUrl = "http://" + window.location.hostname +  (location.port?":"+location.port:"") + window.location.pathname;
	/*
	 * list of connected clients updated by model notifications
	 */
	var sampClients = new Object(); 
	/*
	 * List of hubs which are both Hubs and SAMP clients
	 */
	var hubs = new Array();
	hubs["topcat"] = {
			webStartUrl : "http://www.star.bris.ac.uk/~mbt/topcat/topcat-full.jnlp",
			iconUrl : "http://www.star.bris.ac.uk/~mbt/topcat/tc3.gif",
			webUrl : "http://www.star.bris.ac.uk/~mbt/topcat/",
			description : "Tool for Operations on Catalogues And Tables"
	};
	hubs["aladin"] = {
			webStartUrl : "http://aladin.u-strasbg.fr/java/nph-aladin.pl?frame=get&id=aladin.jnlp",
			iconUrl : "http://aladin.u-strasbg.fr/aladin_large.gif",
			webUrl : "http://aladin.u-strasbg.fr/",
			description : "The Aladin sky atlas and VO Portal"
	};
	/*
	 * Internal flags
	 */
	var waitForHub = false;
	var modalOpen = false;
	var requete = new Object();
	/*
	 * Initiate the div receiving any information from SAMP model
	 */
	var sampModalId = "sampModalId";
	/*
	 * Connection mode: avoid to open the send messaqe window when the user just want to connect the hud
	 */
	var JUSTCONNECT = 1;
	var SENDDATA    = 2;
	var SLEEPING    = 0;
	var mode        = SLEEPING;
	/*
	 * Avoid flooding the hub with multiple connection attemps
	 */
	var lastAttempt = Date.now();
	var REGISTERDELAY = 2000;
	var NOHUB_REGEXP = new RegExp(/no\s+hub/i);
	/*
	 * Methods handling the content of the modal box
	 */
	var getElement = function(eleSuffix) {
		/*
		 * Make sure we are working on the div attached to this view. Its content
		 * is duplicated in the dialog panel and the selectors are lost, 2 hours waste
		 */
		return $('#' + sampModalId + 'Container #'  + sampModalId + eleSuffix);
	};
	var setLoadIcon = function() {
		Processing.show("Talking with the HUB");
//		$('#' + sampModalId + 'Icon').attr("src",
//		"images/ajax-loader.gif");
	};
	var setIvoaIcon = function() {
		Processing.hide();
//		$('#' + sampModalId + 'Icon')
//		.attr("src", "images/ivoa.jpg");
	};
	var setTitle = function(title) {
		getElement('Title ').text(title);
	};
	var setHelp = function(text) {
		getElement('Help').html(text);
	};
	var setPostHelp = function(text) {
		getElement('PostHelp').html(text);
	};
	var addItem = function(text) {
		getElement('ItemList').append(
				"<li>" + text + "</li>");
	};
	var removeAllItems = function() {
		getElement('ItemList').empty();
	};
	var openModalWithClose = function() {
		if (modalOpen == false) {
			modalOpen = true;			
		}
		Modalinfo.dataPanel("Samp Info"
				, getElement('').html()
				, function() {
					WebSamp_mVc.fireCloseModal();
				});
	};
	/*************************************************************************************************
	 * Information display out (private)
	 */
	var makeUrlAbsolute = function (url){
		if( !url.match(/https?:\/\// ) ) {
			return rootUrl + "/" + requete.param;
		} else {
			return url;
		}
	} ;
	var showConnectionOff = function() {
		$("#sampIndicator").attr("class", "sampOff");
	};
	var showConnectionOn = function() {
		$("#sampIndicator").attr("class", "sampOn");
	};
	var showClientList = function(callback) {
		Out.debug("showClientList");
		setIvoaIcon();
		setTitle("Available SAMP Clients");
		setHelp('Below is the list of SAMP clients accepting data<br>\n'
				+ '- Click on the icon of the client you want to send data.<BR>'
				+ '- Click on the broadcast icon if you want your data to be sent to all clients.');
		setPostHelp('');
		removeAllItems();				
		var callback = (requete.type == "oid" )? "WebSamp_mVc.fireSendFileToClient"
				:(requete.type == "voreport") ? "WebSamp_mVc.fireSendUrlToClient"
						:(requete.type == "skyat") ?"WebSamp_mVc.fireSendSkyatToClient"
								: "";
		var found = false;
		for (ident in sampClients) {
			found = true;
			if (sampClients[ident].meta && sampClients[ident].subs) {
				var meta = sampClients[ident].meta;

				var onclick = (callback == "")? "Modalinfo.info(&quot;No message to send&quot;);"
						: callback + "(\"" +  ident + "\");";
				addItem("<img class=clickableicon align=bottom style='height: 32px; border: 0px;' src='"
						+ meta["samp.icon.url"]
						+ "' onclick='" + onclick + "'>"
						+ "<span class=help> <b>"
						+ meta["samp.name"]
						+ "</b> "
						+ meta["samp.description.text"]
						+ " </span><a style='font-color: blue; font-size: small; font-style: italic;' target=_blank href='"
						+ meta["home.page"] + "'>read more...</a>");
			}
		}
		if (found) {
			addItem("<a class=sampOn "
					+ " onclick='" + callback + "(null);'></a>"
					+ "<span class=help> <b>Broadcast</b> to any client");
			openModalWithClose();
		} else {
			fireCloseModal();
			Modalinfo.info("No SAMP Clients Available: Is your Hub still running?");			
			if (fireIsConnected()) showConnectionOff();

		}
	};

	var showHupLauncher = function() {
		Out.debug("showHupLauncher");
		setIvoaIcon();
		setTitle("No running SAMP hub detected");
		setHelp('You need to start a hub before to export data towards VO clients<br> \n'
				+ 'You can either run it by hand or by clicking on one icon below.<br>'
				+ 'If the applcation doesn\'t start, make sure that Java <a target="_blank" href="http://www.java.com/fr/download/faq/java_webstart.xml">Web Start</a> '
				+ 'is properly installed on your machine.');
		setPostHelp('IMPORTANT: Once the hub is running, click on the <span style="diplay: inline-block;width: 23px"><a href="#" class="dlivoalogo"></a>icon</span>  again to send data\n');
		removeAllItems();
		for ( var h in hubs) {
			var hub = hubs[h];
			addItem("<a href='#' onclick=WebSamp_mVc.fireLaunchHub('"
					+ h
					+ "','"
					+ hub.webStartUrl
					+ "');><img class=clickableicon align=middle style='height: 32px; border: 0ps;' src='"
					+ hub.iconUrl
					+ "'></a>"
					+ "<span class=help> <b>"
					+ h
					+ "</b> "
					+ hub.description
					+ " </span><a style='font-color: blue; font-size: small; font-style: italic;' target=_blank href='"
					+ hub.webUrl + "'>read more...</a>");
		}
		openModalWithClose();
	};

	/**************************************************************
	 * Methods handling messages (private)
	 */
	var fireRegisterToHub = function() {
		waitForHub = !listener.controlRegisterToHub().HubRunning;
	};

	/**************************************************************
	 * Methods invoked by the controller (public)
	 */		
	var fireCloseModal = function() {
		if( modalOpen ) {
		modalOpen = false;
		waitForHub = false;
		mode = SLEEPING;
		Modalinfo.close();
		}
	};
	var fireSendOid = function(oid, mtype, displayName) {
		Out.info("Send OID " + oid);
		mode = SENDDATA;
		requete = {type: "oid", param: oid};			
		if (!fireIsConnected()) {
			fireRegisterToHub();
		} else {
			var cpt=0;
			/*
			 * If more than one client: propose a choice window
			 */
			for (ident in sampClients) {
				cpt++;
				showClientList();
				return ;
			}
			showClientList();
		}
	};
	/*
	 * Closely bind to Saada
	 */
	var fireSendFileToClient = function(target) {
		mode = SENDDATA;
		var oid = requete.param;
		var url = rootUrl + "download?oid=" + oid;
		var urlinfo = "getproductinfo?url=" + encodeURIComponent(url);
		$.getJSON(urlinfo,function(data) {
			var ContentType = data.ContentType;
			var fileName = data.ContentDisposition;
			var results = /.*filename=(.*)$/.exec(data.ContentDisposition);
			if( results == null ) {
				fileName = "3XMMDataFile";
			} else {
				fileName = results[1].replace(/"/g, '');
			}
			var mtype = (ContentType.match(/fits/i) )? "table.load.fits": "table.load.votable";
			var message = new Object();
			message["samp.mtype"] = mtype;
			message["samp.params"] = {'table-id': oid, url:url, name: fileName};
			Processing.showAndHide("File sent to SAMP "+  JSON.stringify(message));
			listener.controlSendFileToClient(target, message); 
			fireCloseModal();
		});
	};
	var fireSendVoreport = function(reportUrl, mtype, name) {
		mode = SENDDATA;
		requete = {type: "voreport"
			, param: reportUrl
			, mtype: (mtype == null)?null: mtype
					, name: (name == null)?null: name};			
		Out.info("Send report" + JSON.stringify(requete));
		if (!fireIsConnected()) {
			fireRegisterToHub();
		} else {
			var cpt=0;
			/*
			 * If more than one client: propose a choice window
			 */
			for (ident in sampClients) {
				cpt++;
				showClientList();
				return ;
			}
			fireSendUrlToClient(null);
		}
	};
	var fireSendUrlToClient = function(target) {
		mode = SENDDATA;
		var reportUrl = makeUrlAbsolute(requete.param);

		var message = new Object();
		var rn ;
		if( requete.name == null ) {
			if( requete.param.match(/.*\.fit.*/i) ) {
				rn = "table.load.fits";
			} else {
				rn = "table.load.votable";
			}
		} else{
			rn = requete.name;
		}
		message["samp.mtype"] = (requete.mtype == null)? "table.load.votable": requete.mtype;
		message["samp.params"] = {
				'table-id': rn
				, url:reportUrl
				, name: rn};
		Processing.showAndHide("URL sent to SAMP " +  JSON.stringify(message).replace(/,/g, ",<br>"));
		listener.controlSendUrlToClient(target, message); 
		fireCloseModal();
	};

	var fireSendSkyat = function(ra, dec) {
		mode = SENDDATA;
		Out.info("Send SkyAt " + ra + " " +  dec );
		requete = {type: "skyat", param: {ra: ra, dec: dec}};			
		if (!fireIsConnected()) {
			fireRegisterToHub();
		} else {
			var cpt=0;
			for (ident in sampClients) {
				cpt++;
				showClientList();
				return ;
			}
			fireSendSkyatToClient(null);
		}
	};	
	var fireSendSkyatToClient = function(target) {
		mode = SENDDATA;
		Out.info("Send SkyAt to Client " + target );
		var message = new Object();
		message["samp.mtype"] = "coord.pointAt.sky";
		message["samp.params"] = {ra: String(requete.param.ra), dec: String(requete.param.dec)};
		Processing.showAndHide("Sky position sent to SAMP");
		listener.controlSkyatToClient(target, message);
		fireCloseModal();
	};
	var fireRegisterToHubAttempt = function() {
		var t = Date.now();
		if( (t - lastAttempt) < REGISTERDELAY ) {
			return
		}
		if (mode != SLEEPING && waitForHub && modalOpen ) {
			Out.debug("attempt " + waitForHub );
			fireRegisterToHub();
			lastAttempt = t;
			setTimeout("WebSamp_mVc.fireRegisterToHubAttempt();", REGISTERDELAY);
		} else {
			waitForHub = false;
			if( mode == JUSTCONNECT) {
				fireCloseModal();				
			}
		}
	};
	var fireUnregister = function() {
		if (fireIsConnected()) {
			Out.debug("unregister");
			listener.controlUnregisterToHub();
			showConnectionOff();
		} else {
			Modalinfo.info("Not registered");
		}
	};
	var fireJustConnect = function() {
		mode = JUSTCONNECT;
		if (!fireIsConnected()) {
			fireRegisterToHub();
//			if (!fireIsConnected()) {
//				showHupLauncher();
//			}

		} else {
			fireUnregister();
		}
	};
	var fireIsConnected = function() {
		var retour = listener.controlIsConnected();
		if (retour) {
			showConnectionOn();
		} else {
			showConnectionOff();
		}
		return retour;
	};
	var fireIsHubRunning = function() {
		listener.controlIsHubRunning();
	};
	var showTrackerReply = function(id, action, data) {
		Out.debug("showTrackerReply " + id + " " +action);
		waitForHub = false;
		showConnectionOn();
		var removed = "";
		var completed = "";
		// Hub is not considered as a SAMP client
		if (!id || id.match(/hub/i)) {
			return;
		}
		if (action == "unregister") {
			if (sampClients[id]) {
				delete sampClients[id];
				removed = id;
			}
		} else {
			if (!sampClients[id]) {
				sampClients[id] = new Object();
			}
			if (action == "meta") {
				sampClients[id].meta = data;
			} 
			// Only accept to deal with clients accepting VOtable or FITS
			else if (action == "subs") {
				if (data["table.load.fits"] && data["table.load.votable"] &&  data["coord.pointAt.sky"] ) {
					sampClients[id].subs = data;
				} else {
					delete sampClients[id];
				}
			}
			if (sampClients[id] && sampClients[id].meta && sampClients[id].subs)
				completed = id;
		}
		/*
		 * Display the client list only if it has changed and if its
		 * content is complete
		 */
		if (mode == SENDDATA && (removed != "" || completed != "") ) {
			showClientList();
		}
	};
	var showHubError = function(message) {
		Out.debug("showHubError " + message + " " + NOHUB_REGEXP.test(message));
		if( NOHUB_REGEXP.test(message) == false) {
			return
		}
		waitForHub = true;
		showConnectionOff();
		setIvoaIcon();			
		showHupLauncher();
		setTimeout(WebSamp_mVc.fireRegisterToHubAttempt, REGISTERDELAY);
	};		
	var fireLaunchHub = function(name, url) {
		setLoadIcon();
		PageLocation.changeLocation(url);
		setIvoaIcon();
	};
	var init = function(name, url, description){
		/*
		 * Must be done from $.ready, once  document.body is complete 
		 */
		$(document.body).append(
				"<div id=" + sampModalId + "Container>"
				+ "<div id="
				+ sampModalId
				+ " style='width: 99%; display: none;'>"
				+ '<a id="' // dummy a just to have the image referenced by the css
				+ sampModalId
				+ 'Icon" class="ivoalogo"></a>'
				+ '<span id='
				+ sampModalId
				+ 'Title style="display: inline; font-size: 1.5em;font-weight: bold;"> TITLE</span><BR>'
				+ '<div><ul id=sampClientListItems></ul></div>'
				+ '<span id=' + sampModalId
				+ 'Help class=help></span>'
				+ '<ul id=' + sampModalId
				+ 'ItemList></ul>' + '<span id='
				+ sampModalId
				+ 'PostHelp class=help></span>'
				+ "</div>"
				+ "</div>"
		);
		new WebSamp_mvC(WebSamp_mVc, new WebSamp_Mvc(name, url, description));	
	};

	/* Exports. */
	var jss = {};
	jss.init = init;
	jss.fireCloseModal = fireCloseModal;
	jss.fireSendOid = fireSendOid; //
	jss.fireSendFileToClient = fireSendFileToClient;
	jss.fireSendVoreport = fireSendVoreport;
	jss.fireSendUrlToClient = fireSendUrlToClient;
	jss.fireSendSkyat = fireSendSkyat; //
	jss.fireSendSkyatToClient = fireSendSkyatToClient;
	jss.fireRegisterToHubAttempt = fireRegisterToHubAttempt;//
	jss.fireUnregister = fireUnregister; //
	jss.fireJustConnect = fireJustConnect;
	jss.fireIsConnected = fireIsConnected; //
	jss.fireIsHubRunning = fireIsHubRunning;//
	jss.showTrackerReply = showTrackerReply; //
	jss.showHubError = showHubError;
	jss.fireLaunchHub = fireLaunchHub;
	jss.addListener = addListener;
	return jss;
}();


console.log('=============== >  WebSamp_v.js ');


WebSamp_mvC = function(view,model){
	/**
	 * listen to the view
	 */

	var vlist = {
			controlIsConnected: function(){
				return model.isConnected();
			},
			controlRegisterToHub: function(){
				return  model.registerToHub();
			},
			controlUnregisterToHub: function(){
				return model.unregisterToHub();
			},
			controlIsHubRunning: function(){
				model.isHubRunning();
			},
			controlSendFileToClient: function(target, message){
				model.sendMessageToClient(target, message);
			},
			controlSendUrlToClient: function(target, message){
				model.sendMessageToClient(target, message);
			},
			controlSkyatToClient: function(target, message){
				model.sendMessageToClient(target, message);
			}
	};

	view.addListener(vlist);

	var mlist = {
			controlTrackerReply: function(id, type, data){
				view.showTrackerReply(id, type, data);
			},
			controlHubError: function(message){
				view.showHubError(message);
			},
			isInit : function(attributesHandlers){
				view.displayTable(attributesHandlers);
			}
	};

	model.addListener(mlist);
};

console.log('=============== >  WebSamp_c.js ');

/**
 * KWConstraint_Mvc: Model of individual KW editor
 * Parameters are received as a Javascript object with the following field:
 * - this.isFirst : NO AND/OR prepended if true
 * - this.attributeHandler: Attribute Handler attached to this MVC
 * - this.editorModel: ConstQEditor_Mvc instance owning this object
 * - defValue: Default this.operand to be applied to the constraint
 */
function KWSimpleConstraint_Mvc(params){

	this.listener = null;
	this.operators = new Array();
	this.range = {type: "not set", values: []};
	this.andors = new Array();

	/*
	 * Parameters decoding
	 */
	this.attributeHandler = params.attributeHandler;
	this.editorModel  = params.editorModel;
	this.defaultValue = params.defValue;
	this.isFirst      = params.isFirst;

	this.operator= '' ;
	this.operand = '';
	this.andor = '';
	this.booleansupported = false;
};

KWSimpleConstraint_Mvc.prototype = {
		addListener: function(list){
			this.listener = list;
		},
		processEnterEvent: function() {
			if( this.editorModel != null ) this.editorModel.updateQuery();
		},
		processRemoveConstRef: function(ahname) {
			if( this.editorModel != null ) this.editorModel.processRemoveConstRef(ahname);
		},
		removeAndOr: function() {
			this.andor = "";
		},
		notifyInitDone: function(){
			this.listener.isInit(this.attributeHandler, this.operators ,this.andors, this.range, this.defaultValue);
		},
		notifyTypoMsg: function(fault, msg) {
			this.fault = fault;
			this.listener.notifyFault(fault);
			if( this.editorModel != null ) this.editorModel.notifyTypoMsg(fault,msg);
		},		
		notifyRunQuery: function() {
			if( this.editorModel != null ) this.editorModel.notifyRunQuery();
		},
		getADQL: function(attQuoted) {
			return this.listener.controlAhName(this.attributeHandler);
		},
		processRemoveFirstAndOr : function(key) {			
			if( this.attributeHandler.type != 'orderby') {
				if( this.editorModel != null ) this.editorModel.processRemoveFirstAndOr(key);
			}
		},
		isString: function(){
			return (this.attributeHandler.type != null)? this.attributeHandler.type.match(/(string)|(text)|(varchar)|char/i): false;
		}

};
/**
 * Simple constraint for TAP: just quote filed names to be compliant with ADQL
 * @param params
 * @returns {TapKWSimpleConstraint_mVc}
 */
function TapKWSimpleConstraint_Mvc(params){
	KWSimpleConstraint_Mvc.call(this, params);
}
TapKWSimpleConstraint_Mvc.prototype  = Object.create(KWSimpleConstraint_Mvc.prototype, {
	getADQL: {
		value : function() {
			return this.listener.controlAhName(this.attributeHandler).quotedTableName();
		}
	}
});


/**
 * 
 * @param params
 * @returns
 */
function KWConstraint_Mvc(params){
	KWSimpleConstraint_Mvc.call(this, params);
	/*
	 * fault in constraint 
	 */
	this.fault = 0;

	if( this.attributeHandler.nameattr != null 
			&& (this.attributeHandler.nameattr == 'Cardinality' || this.attributeHandler.nameattr.startsWith('Qualifier '))) {
		this.operators = ["=", "!=", ">", "<", "][", "]=[", "[]", "[=]"];			
		this.andors = [];
	} else if( this.attributeHandler.type == 'Select' ) {
		this.operators = [];
		this.andors = [];
	} else if( this.attributeHandler.type == 'ADQLPos' ) {
		this.operators = ["inCircle", "inBox"];
		this.andors = ["AND", "OR"];

	} else if( this.attributeHandler.type == 'boolean' ) {
		this.operators = ['=', 'IS NOT NULL', 'IS NULL'];
		this.andors = ['AND', 'OR'];
	} else if( !this.isString() ) {
		this.operators = ["=", "!=", ">", "<", "between", 'IS NOT NULL', 'IS NULL'];
		this.andors = ['AND', 'OR'];
	} else {
		this.operators = ["=", "!=", "LIKE", "NOT LIKE", 'IS NOT NULL' , 'IS NULL'];
		this.andors = ['AND', 'OR'];
	}

	if( this.isFirst == true ) {
		this.andors = [];
	}
	if( this.attributeHandler.type == 'orderby' ) {
		this.operators = [];
		this.andors = [];
	}

	if( this.attributeHandler.range != null ) {
		/*
		 * List of values
		 */
		this.range.type = "list";
		if (this.attributeHandler.range.values!=undefined){
			if(this.attributeHandler.range.values.length>0){
				for(var i=0; i<this.attributeHandler.range.values.length; i++){
					var choix = { value: this.attributeHandler.range.values[i] };
					this.range.values[this.range.values.length] = choix;
				}
			} else {
				var choix = { value: this.attributeHandler.range.values };
				this.range.values[this.range.values.length] = choix;
			}
			/*
			 * range of values
			 */
		} else {
			this.range.type = "range";
			if(this.attributeHandler.range.min!=undefined){
				var choix = { value: "min " + this.attributeHandler.range.min};
				this.range.values[this.range.values.length] =  choix;
			} 
			if (this.attributeHandler.range.max!=undefined){
				var choix = { value:  "max " +   this.attributeHandler.range.max};
				this.range.values[this.range.values.length] =choix;
			}
		}
	}

};

KWConstraint_Mvc.prototype  = Object.create(KWSimpleConstraint_Mvc.prototype, {
	processEnterEvent: {
		value : function(ao, op, opd, unit) {

			this.andor = ao;
			if( this.attributeHandler.type == 'orderby') {
				this.editorModel.updateQuery();
				return;
			} else if( this.isString() ) {
				if( !this.checkAndFormatString(op, opd) ) {
					this.editorModel.updateQuery();
					return;
				}
			} else if( this.attributeHandler.type == 'boolean') {
				if( !this.checkAndFormatString(op, opd) ) {
					this.editorModel.updateQuery();
					return;
				}
			} else {
				if( !this.checkAndFormatNum(op, opd) ) {
					this.editorModel.updateQuery();
					return;
				}			
			}
			this.notifyTypoMsg(0, this.operator + ' ' + this.operand);				
			if( this.andors.length == 0 ) {
				this.processRemoveFirstAndOr();
			}
			this.editorModel.updateQuery();
		}
	},
	checkAndFormatNum: {
		value : function(op, opd) {
			/*
			 * Case of select items in ADQL
			 */
			if( op == null || op.length == 0 ) {
				this.operator = "";
				this.operand = "";
				return 1 ;			
			}
			if( op == 'IS NULL' ) {
				this.operator = 'IS NULL';
				this.operand = '';
				return 1;								
			} else if( /^\s*$/.test(opd)  ) {
				if( this.attributeHandler.nameattr == 'Cardinality' || this.attributeHandler.nameattr.startsWith('Qualifier ')) {
					this.notifyTypoMsg(1, 'Numeric operand required');
					return 0 ;
				} else {
					this.operator = 'IS NOT NULL';
					this.operand = '';
					return 1;
				}
			} else if( op == 'between' ) {
				var words = opd.split(' ') ;
				if( words.length != 3 || !/and/i.test(words[1]) ||
						words[0].length == 00 || words[2].length == 00 ||
						isNaN(words[0]) || isNaN(words[2]) ) {
					this.notifyTypoMsg(1, 'Operator "' + op + '" requires an operand of form "num1 and num2"');
					return 0 ;
				}
				this.operator = op;
				this.operand = words[0] + ' AND ' + words[2];						
				return 1 ;
			} else if( op == 'inCircle' || op == 'inBox')  {
				var area = null;
				if( this.attributeHandler.nameattr.startsWith('POSLIST:')) {
					area = opd.split(',');
					var n = this.attributeHandler.nameattr.replace("POSLIST:", "upload.");
					area = [n + ".pos_ra_csa", n + ".pos_dec_csa", area[area.length-1]];

				} else {
					area = opd.split(',');
					if( area.length != 3 || isNaN(area[0]) || isNaN(area[1]) )  {
						this.notifyTypoMsg(1, 'Search area must be like :alpha,delta,size"');					
						return 0 ;
					}
				}
				if(  isNaN(area[2]) ) {
					this.notifyTypoMsg(1, 'Search area must be like :alpha,delta,size"');					
					return 0 ;
				}
				if( op == 'inCircle') {
					this.operator = "CIRCLE('ICRS', " + area[0]+ ", " +area[1] + ", " + area[2]/60+ ")";
					this.operand = "";
				} else {
					this.operator = "BOX('ICRS', " + area[0]+ ", " +area[1] + ", " + area[2]+  ", " + area[2]/60  +")";
					this.operand = "";					
				}
				return 1 ;
			} else if( isNaN(opd) && this.attributeHandler.type != 'boolean' ) {
				this.notifyTypoMsg(1, 'Operator "' + op + '" requires a single numeric operand');
				return 0 ;
			} else {
				this.operator = op;
				this.operand = opd;
				return 1 ;			
			}
		}
	},
	checkAndFormatString: {
		value : function(op, opd) {
			if( op == 'IS NULL' ) {
				this.operator = 'IS NULL';
				this.operand = '';
				return 1;								
			} else if( /^\s*$/.test(opd)  ) {
				this.operator = 'IS NOT NULL';
				this.operand = '';
				return 1;				
			} else {
				if ( /^\s*'.*'\s*$/.test(opd)  ) {
					this.operand = opd;
				} else {
					this.operand = "'" + opd + "'";
				}
				this.operator = op;
				return 1;			
			}
		}
	},
	processRemoveConstRef: {
		value : function(ahname) {
			this.editorModel.processRemoveConstRef(ahname);
		}
	},
	removeAndOr: {
		value : function() {
			this.andor = "";
		}
	},
	getADQL: {
		value : function(attQuoted) {
			if( this.fault ) {
				return null;
			}
			if(  this.attributeHandler.nameattr.startsWith('Qualifier ')) {
				return 'Qualifier{ ' + this.attributeHandler.nameattr.split(' ')[1] + this.operator + ' ' + this.operand + '}';
			} else if( this.operator.startsWith('CIRCLE') || this.operator.startsWith('BOX'))  {
				//				CONTAINS(POINT('ICRS GEOCENTER', "_s_ra", "_s_dec"), BOX('ICRS GEOCENTER', 'dsa', 'dsad', 'dsa', 'dsad')) = 'true';
				var coordkw = this.attributeHandler.description.split(' ');
				var bcomp = ( this.booleansupported )? "'true'" :  "1";
				return this.andor + " CONTAINS(POINT('ICRS', " + coordkw[0].quotedTableName() + ", " +  coordkw[1].quotedTableName() + "), "
				+ this.operator + ") = " + bcomp;
			} else if( attQuoted ){ 
				return this.andor + ' "' + 	this.listener.controlAhName(this.attributeHandler) + '" ' + this.operator + ' ' + this.operand;
			} else {
				return this.andor + ' ' + this.listener.controlAhName(this.attributeHandler) + ' ' + this.operator + ' ' + this.operand;
			}
		}
	}

});

/**
 * Separate behaviour specific to Saada from those specific to TAP (must be continued)
 * @param params
 * @returns {TapKWConstraint_Mvc}
 */
function TapKWConstraint_Mvc(params){
	KWConstraint_Mvc.call(this, params);
}
TapKWConstraint_Mvc.prototype  = Object.create(KWConstraint_Mvc.prototype, {
	getADQL: {
		value : function(attQuoted) {
			if( this.fault ) {
				return null;
			}
			if( this.operator.startsWith('CIRCLE') || this.operator.startsWith('BOX'))  {
				//				CONTAINS(POINT('ICRS GEOCENTER', "_s_ra", "_s_dec"), BOX('ICRS GEOCENTER', 'dsa', 'dsad', 'dsa', 'dsad')) = 'true';
				var coordkw = this.attributeHandler.description.split(' ');
				var bcomp = ( this.booleansupported )? "'true'" :  "1";
				return this.andor + " CONTAINS(POINT('ICRS', " + coordkw[0].quotedTableName() + ", " +  coordkw[1].quotedTableName() + "), "
				+ this.operator + ") = " + bcomp;
			} else if( attQuoted ){ 
				return this.andor + ' "' + 	this.listener.controlAhName(this.attributeHandler).quotedTableName() + '" ' + this.operator + ' ' + this.operand;
			} else {
				return this.andor + ' ' + this.listener.controlAhName(this.attributeHandler).quotedTableName() + ' ' + this.operator + ' ' + this.operand;
			}
		}
	}

});


/**
 * Subclass of KWConstraint_Mvc: manage UCDS based constraint
 * @param params
 */
function UCDConstraint_Mvc(params){
	KWConstraint_Mvc.call(this, params);
	this.operators = ["=", "!=", ">", "<", "][", "]=[", "[]", "[=]"];
	this.unit = "";

};


UCDConstraint_Mvc.prototype = Object.create(KWConstraint_Mvc.prototype, {
	getADQL: {
		value: function(attQuoted) {
			if( this.fault ) {
				return null;
			} else {
				return "[" + this.attributeHandler.ucd + '] ' + this.operator + ' ' + this.operand + ' ' + this.unit;
			}
		}
	},
	processEnterEvent:  {
		value: function(ao, op, opd, unit) {
			this.andor = "AND";
			this.unit = (unit == null || unit == "")?"": ("[" + unit + "]");
			if( opd == "" ) { 
				this.notifyTypoMsg(1, 'Operand is required with UCDs');
				this.editorModel.updateQuery();
				return;
			} else if( op == '][' || op == ']=[' || op == '[]' || op == '[=]') {
				this.operator = op;
				var myArray = opd.match(/^((?:[^\s]+)|(?:'.*'))\s+and\s+((?:[^\s]+)|(?:'.*'))$/);
				if ( myArray != null ) {
					var o1 = myArray[1];
					var o2 = myArray[2];
					o1 = (isNaN(o1) && !/^\s*'.*'\s*$/.test(o1))? "'" + o1 + "'": o1;
					o2 = (isNaN(o2) && !/^\s*'.*'\s*$/.test(o2))? "'" + o2 + "'": o2;
					this.operand = '(' + o1 + ' , ' + o2 + ')';												
				}  else {
					this.notifyTypoMsg(1, 'The operator ' + op + ' requires an operand like "num1 and num2"');
					this.editorModel.updateQuery();
					return;
				}
			} else {
				this.operator = op;
				var o1 = (isNaN(opd) && !/^\s*'.*'\s*$/.test(opd))? "'" + opd + "'": opd;
				this.operand =  o1;												
			}

			this.notifyTypoMsg(0, this.operator + ' ' + this.operand + ' [' + unit + ']');				
			if( this.andors.length == 0 ) {
				this.processRemoveFirstAndOr();
			}
			this.editorModel.updateQuery();
		}
	}
});
/**
 * Subclass of KWConstraint_Mvc: manage position based constraint
 * @param params
 */
function PosConstraint_Mvc(params){
	KWConstraint_Mvc.call(this, params);
	this.operators = [];
};


PosConstraint_Mvc.prototype = Object.create(KWConstraint_Mvc.prototype, {
	processEnterEvent:  {
		value: function(ao, op, opd) {
			this.andor = "AND";
			if( opd == "" ) {
				this.operator = 'IS NOT NULL';
			} else if( op == '][' || op == ']=[' || op == '[]' || op == '[=]') {
				this.operator = op;
				var myArray = opd.match(/^((?:[^\s]+)|(?:'.*'))\s+and\s+((?:[^\s]+)|(?:'.*'))$/);
				if ( myArray != null ) {
					var o1 = myArray[1];
					var o2 = myArray[2];
					o1 = (isNaN(o1) && !/^\s*'.*'\s*$/.test(o1))? "'" + o1 + "'": o1;
					o2 = (isNaN(o2) && !/^\s*'.*'\s*$/.test(o2))? "'" + o2 + "'": o2;
					this.operand = '(' + o1 + ' , ' + o2 + ')';												
				}  else {
					this.notifyTypoMsg(1, 'Operator ' + op + ' requires an operand like "num1 and num2"');
					this.editorModel.updateQuery();
					return;
				}
			} else {
				this.operator = op;
				var o1 = (isNaN(opd) && !/^\s*'.*'\s*$/.test(opd))? "'" + opd + "'": opd;
				this.operand =  o1;												
			}
			var str = this.getADQL();
			str = (str.length > 48)? (str.substring(0, 47) + "..."): str;
			this.notifyTypoMsg(0, str);				
			if( this.andors.length == 0 ) {
				this.processRemoveFirstAndOr();
			}
			this.editorModel.updateQuery();
		}
	},
	getADQL: {
		value: function() {
			if( this.fault ) {
				return null;
			} 
			if( this.attributeHandler.nameattr == "region") {
				return 'isInRegion("' + this.attributeHandler.value + '", 0' 
				+ ', ' + ((this.attributeHandler.frame == "FK5")? "J2000":
					(this.attributeHandler.frame == "FK4")? "J1950": '-')
					+ ', ' + this.attributeHandler.frame + ')';

			} else {
				return 'isInCircle("' + this.attributeHandler.nameorg + '", ' 
				+ this.attributeHandler.radius  
				+ ', ' + ((this.attributeHandler.frame == "FK5")? "J2000":
					(this.attributeHandler.frame == "FK4")? "J1950": '-')
					+ ', ' + this.attributeHandler.frame + ')';
			}
		}
	}

});

/**
 * Subclass of KWConstraint_Mvc: manages catalogue based constraint
 * @param params
 */
function CatalogueConstraint_Mvc(params){
	KWConstraint_Mvc.call(this, params);
	this.qualifier = params.qualifier;
	this.operators = [">", "<", "][", "[]"];
};


CatalogueConstraint_Mvc.prototype = Object.create(KWConstraint_Mvc.prototype, {
	getADQL: {
		value: function(attQuoted) {
			if( this.fault ) {
				return null;
			}
			var retour = "   AssObjClass{" + this.attributeHandler.CLASSNAME + "}";
			if( this.operand != "" ) {
				retour += ", Qualifier{" + this.qualifier +  ' ' + this.operator + ' ' + this.operand + "}";
			}
			return retour;
		}
	},
	processEnterEvent:  {
		value: function(ao, op, opd, unit) {
			/*
			 * No parameter: constraint to be removed
			 */
			if( ao == undefined && op == undefined && opd == undefined) {
				this.editorModel.updateQuery();				
			}
			this.andor = "AND";
			this.unit = (unit == null || unit == "")?"": ("[" + unit + "]");
			if( opd == "" ) { 
				this.notifyTypoMsg(0, 'at any distance');
			} else if( op == ']['  || op == '[]' ) {
				this.operator = op;
				var myArray = opd.match(/^\s*([^\s]+)\s+and\s+([^\s]+)\s*$/);
				if ( myArray != null ) {
					var o1 = myArray[1];
					var o2 = myArray[2];
					if(isNaN(o1) || isNaN(o1) ) {
						this.notifyTypoMsg(1, 'Distance requires a single operands');
						return;
					}
					this.operand = '(' + o1 + ' , ' + o2 + ')';												
					this.notifyTypoMsg(0, this.operator + ' ' + this.operand );				
				}  else {
					this.notifyTypoMsg(1, 'The operator ' + op + ' requires an operand like "num1 and num2"');
					return;
				}
			} else if( isNaN(opd) ) {
				this.notifyTypoMsg(1, 'Distance requires a single numeric operand');
				return ;
			} else {
				this.operator = op;
				this.operand =  opd;												
				this.notifyTypoMsg(0, this.operator + ' ' + this.operand );				
			}
			this.editorModel.updateQuery();
		}
	}
});

/**
 * Subclass of KWConstraint_Mvc: manages catalogue based constraint
 * @param params
 */
function CrossidConstraint_Mvc(params){
	CatalogueConstraint_Mvc.call(this, params);
};


CrossidConstraint_Mvc.prototype = Object.create(CatalogueConstraint_Mvc.prototype, {
	processEnterEvent:  {
		value: function(ao, op, opd, unit) {
			/*
			 * No parameter: constraint to be removed
			 */
			if( ao == undefined && op == undefined && opd == undefined) {
				this.editorModel.updateQuery();				
			}
			this.andor = "AND";
			this.unit = (unit == null || unit == "")?"": ("[" + unit + "]");
			if( opd == "" ) { 
				this.notifyTypoMsg(0, 'with any probality');
			} else if( op == ']['  || op == '[]' ) {
				this.operator = op;
				var myArray = opd.match(/^\s*([^\s]+)\s+and\s+([^\s]+)\s*$/);
				if ( myArray != null ) {
					var o1 = myArray[1];
					var o2 = myArray[2];
					if(isNaN(o1) || isNaN(o1) ) {
						this.notifyTypoMsg(1, 'Distance requires a single operands');
						return;
					} else if( o1 <0 || o1 > 1 || o2 < 0 || o2 > 1 ){
						this.notifyTypoMsg(1, 'Probability must be between 0 and 1');
						return ;
					}
					this.operand = '(' + o1 + ' , ' + o2 + ')';												
					this.notifyTypoMsg(0, this.operator + ' ' + this.operand );				
				}  else {
					this.notifyTypoMsg(1, 'The operator ' + op + ' requires an operand like "num1 and num2"');
					return;
				}
			} else if( isNaN(opd) ) {
				this.notifyTypoMsg(1, 'Probability requires a single numeric operand between 0 and 1');
				return ;
			} else if( opd <0 || opd > 1){
				this.notifyTypoMsg(1, 'Probability must be between 0 and 1');
				return ;
			} else {
				this.operator = op;
				this.operand =  opd;												
				this.notifyTypoMsg(0, this.operator + ' ' + this.operand );				
			}
			this.editorModel.updateQuery();
		}
	}
});



console.log('=============== >  KWConstraint_m.js ');

function KWSimpleConstraint_mVc(params){
	/*
	 * Params decoding
	 */
	this.rootId = params.divId;
	this.constListId = params.constListId;		
	this.treePath = params.treePath; // facultatif
	this.fieldName = "";
	/*
	 * who is listening to us?
	 */
	this.listener = null;
	/*
	 * Connect to the controler and to the model
	 */
	new KWConstraint_mvC(new KWSimpleConstraint_Mvc(params), this);	
}

KWSimpleConstraint_mVc.prototype = {
		addListener : function(list){
			this.listener = list;
		},
		fireInit : function() {
			this.listener.controlInit();
		},

		initForm : function(ah, operators ,andors,range,default_value){
			var that = this;
			$('#' + this.constListId).append("<div class='kwConstraint' id=" + this.rootId + " style='overflow: hidden;'>");
			var baseSelector  = '#' + this.constListId + ' div[id="' + this.rootId + '"]';
			var rootDiv = $(baseSelector);
			rootDiv.append('&nbsp;<a id="' + this.rootId + '_close" href="javascript:void(0);" class=closekw title="Remove this Constraint"></a>&nbsp;');
			rootDiv.append('<span id="' + this.rootId + '_name" style="float: left;width: 190px">' + this.getAhName(ah) + '</span>&nbsp;');
			$('#' + this.constListId).append("</div>");	

			var closeInput = $('#' + this.constListId + ' a[id="' + this.rootId + '_close"]');

			closeInput.click(function() {
				rootDiv.remove();
				that.fireRemoveFirstAndOr(this.rootId);
				that.fireEnterEvent();
			});
			this.fireEnterEvent(null, null, null);
		},
		fireGetADQL : function(attQuoted){
			return this.listener.controlGetADQL(attQuoted);
		},	
		fireEnterEvent : function(andor, operator, operand, unit){
			this.listener.controlEnterEvent(andor, operator, operand, unit);
		},
		fireRemoveAndOr :  function() {
			$('#' + this.rootId + "_andor" ).remove();
			this.listener.controlRemoveAndOr(this.rootId);
		},
		fireRemoveFirstAndOr : function(){
			this.listener.controlRemoveFirstAndOr(this.rootId);
		},
		getAhName: function(ah){
			var name = "";
			if( this.treePath != undefined && !ah.nameattr.startsWith("POSLIST") ){
				name =  this.treePath.schema + "." + this.treePath.table + "." + ah.nameorg ;
			} else {
				name =  ah.nameattr ;
			}
			this.fieldName = name;
			return this.fieldName;
		}
};

/**
 * Simple constraint for TAP: just quote filed names to be compliant with ADQL
 * @param params
 * @returns {TapKWSimpleConstraint_mVc}
 */
function TapKWSimpleConstraint_mVc(params){
	KWSimpleConstraint_mVc.call(this, params);
	new KWConstraint_mvC(new TapKWSimpleConstraint_Mvc(params), this);	
}
TapKWSimpleConstraint_mVc.prototype = Object.create(KWSimpleConstraint_mVc.prototype, {});

/**
 * KWConstraint_mVc: View of the KW constraint editor 
 * Parameters are received as a Javascript object with the following field:
 * - divId      : ID of the div containing this constraint. 
 *                Given from outside to avoid conflict with JQuery selectors
 * - constListId: ID of the div containing the list of constraints
 * The following parameters are used by the model:
 *  - isFirst : NO AND/OR prepended if true
 * - attributeHandler: Attribute Handler attached to this MVC
 * - editorModel: ConstQEditor_Mvc instance owning this object
 * - defValue: Default operand to be applied to the constraint

 * @returns {KWConstraint_mVc}
 */
function KWConstraint_mVc(params){
	KWSimpleConstraint_mVc.call(this, params);
	new KWConstraint_mvC(new KWConstraint_Mvc(params), this);	
}

KWConstraint_mVc.prototype = Object.create(KWSimpleConstraint_mVc.prototype, {
	initForm : {
		value: function(ah, operators ,andors,range,default_value){

			var that = this;
			$('#' + this.constListId).append("<div class='kwConstraint' id=" + this.rootId + " style='float: none;'>");
			var baseSelector  = '#' + this.constListId + ' div[id="' + this.rootId + '"]';
			var rootDiv = $(baseSelector);
			rootDiv.append('&nbsp;<a id="' + this.rootId + '_close" href="javascript:void(0);" class=closekw title="Remove this Constraint"></a>&nbsp;');
			/*
			 * AND/OR operators
			 */
			if( andors.length > 0 ) {
				var select='<select id="' + this.rootId + '_andor" style="font-size: small;font-family: courier;\">';
				for( var i=0 ; i<andors.length; i++ ) {
					var op = andors[i];
					select += '<option value=' + op + '>' +op + '</option>';
				}	
				select += '</select>&nbsp;';
				rootDiv.append(select);
			}
			rootDiv.append('<span id="' + this.rootId + '_name">' + this.getAhName(ah) + '</span>&nbsp;');
			/*
			 * Logical operators
			 */
			if( operators.length > 0 ) {
				var select='<select id="' + this.rootId + '_op" style="font-size: small;font-family: courier;">';
				for( i=0 ; i<operators.length; i++ ) {
					var op = operators[i];
					var selected = '';
					if( op == '>' ) {
						op = '&gt;';
						if( ah.nameattr == 'Cardinality' ) {
							selected = 'selected';
						} 
					} else if( op == '<' ) {
						op = '&lt;';
					}
					select += '<option value="' + op + '" ' + selected + '>' +op + '</option>';
				}	
				if( range != undefined && range.type == "list" ){
					for( var v=0 ; v<range.values.length ; v++ ) {
						var txt = (ah.type == "String")? "'" + range.values[v].value + "'": range.values[v].value;
						select += '<option value="= ' + txt + '">= ' + txt + '</option>';
					}
				}
				select += '</select>';
				rootDiv.append(select);

				rootDiv.append('<input type=text id="' + this.rootId 
						+ '_val" class=inputvalue style="width:100px; font-size: small;font-family: courier;" value="' 
						+ default_value + '">');
				if( range != undefined && range.values.length>0 ){
					this.loadRange( '#' + this.constListId + ' input[id="' + this.rootId + '_val"]',range);
				}
			}
			$('#' + this.constListId).append("</div>");	

			var opSelected = '#' + this.constListId + ' select[id="' + this.rootId + '_op"] option:selected';
			var opInput    = $('#' + this.constListId + ' select[id="' + this.rootId + '_op"]');
			var andorInput = $('#' + this.constListId + ' select[id="' + this.rootId + '_andor"]');
			var andorInputOpt = $('#' + this.constListId + ' select[id="' + this.rootId + '_andor"] option:selected');
			var valInput   = $('#' + this.constListId + ' input[id="' + this.rootId + '_val"]');
			var closeInput = $('#' + this.constListId + ' a[id="' + this.rootId + '_close"]');
			closeInput.click(function() {
				rootDiv.remove();
				that.fireRemoveFirstAndOr(this.rootId);
				if( ah.nameattr.startsWith('Qualifier') || ah.nameattr.startsWith('Cardinality')) {
					that.fireRemoveConstRef(ah.nameattr); 
				}
				that.fireEnterEvent();
				that.fireTypoMsg(false, '');
			});
			opInput.change(function() {
				var v = this.value;
				var regex = /=\s+(.+)/;
				var results = regex.exec(v);
				if( results ) {	
					$('#' + that.constListId + ' select[id="' + that.rootId + '_op"] option[value="="]').prop('selected', true);
					valInput.val(results[1]);
				}
				that.fireEnterEvent(andorInputOpt.text()
						, this.value
						, valInput.val());				
			});
			andorInput.change(function() {
				that.fireEnterEvent(this.value
						, $(opSelected).text()
						, valInput.val());				
			});
			valInput.keyup(function(event) {
				/*
				 * Run the query is CR is typed in a KW editor
				 */
				if (event.which == '13') {
					if( that.isConstraintOK() ) {
						that.fireRunQuery();
					} else {
						Modalinfo.error("Current contraint is not valid: cannot run the query");
					}
				} else {
					that.fireEnterEvent(andorInputOpt.text()
							, $(opSelected).text()
							, this.value);
				}
			});
			valInput.click(function(event) {
				that.fireEnterEvent(andorInputOpt.text()
						, $(opSelected).text()
						, this.value);
			});
			valInput.on('input', function(event) {
				that.fireEnterEvent(andorInputOpt.text()
						, $(opSelected).text()
						, this.value);
			});
			this.fireEnterEvent(andorInputOpt.text()
					, $(opSelected).text()
					,valInput.val());
		}
	},
	isConstraintOK: {
		value: function(){		
			return ($('#' + this.constListId).parent().find("span.typomsg_ok:first").length != 0);
		}
	},
	loadRange : {
		value:  function(id,range) {
			var that = this;
			$(id).autocomplete({
				source: range.values,
				minLength: 0,
				select: function(event, ui) { 				
					var regex = /(?:(?:min)|(?:max))\s+(.+)/;
					var results = regex.exec(ui.item.value);
					if( results ) {
						ui.item.value = results[1];
					} 
					that.fireEnterEvent($('#' + that.constListId + ' select[id="' +  that.rootId + '_andor"] option:selected').text()
							, $('#' + that.constListId + ' select[id="' +  that.rootId + '_op"] option:selected').text()
							, ui.item.value);
				}
			});
		}
	},
	drawFault :  {
		value: function(fault){
			var d = $('#' + this.constListId + ' div[id="' + this.rootId + '"]');
			if( fault ) {
				d.css("background-color", "pink");
			} else {
				d.css("background-color", "");
			}
		}
	},
	fireRemoveConstRef :  {
		value: function(ahname){
			this.listener.controlRemoveConstRef(ahname);
		}	
	},
	fireGetADQL :  {
		value: function(attQuoted){
			return this.listener.controlGetADQL(attQuoted);
		}
	},
	fireTypoMsg :  {
		value: function(fault, msg){
			this.listener.controlTypoMsg(fault, msg);
		}
	},
	fireRunQuery:  {
		value:  function() {
			this.listener.controlRunQuery();
		}
	}
});

/**
 * Subclass of KWConstraint_mVc modeling a UCD based constraint
 * @param params
 */
function UCDConstraint_mVc(params){
	KWConstraint_mVc.call(this, params);
	new KWConstraint_mvC(new UCDConstraint_Mvc(params), this);	
};


UCDConstraint_mVc.prototype = Object.create(KWConstraint_mVc.prototype, {
	initForm : { 
		value: function(ah, operators ,andors,range,default_value){
			if( ah == null ) {
				Modalinfo.error("Attempt to init a UCDConstraitn with a attribute handler null");
				return;
			}
			var that = this;
			$('#' + this.constListId).append("<div class='kwConstraint' id='" + this.rootId + "' style='float: none;'>");
			var baseSelector  = '#' + this.constListId + ' div[id="' + this.rootId + '"]';
			var valSelector   = '#' + this.constListId + ' input[id="' + this.rootId + '_val"]';
			var rootDiv = $(baseSelector);
			rootDiv.append('&nbsp;<a id="' + this.rootId + '_close" href="javascript:void(0);" class=closekw title="Remove this Constraint"></a>&nbsp;');
			rootDiv.append('<span id=' + this.rootId + '_name>' + ah.ucd + '</span><br>');
			/*
			 * Logical operators
			 */
			var select='<select id="' + this.rootId + '_op" style="font-size: small;font-family: courier;">';
			for( var i=0 ; i<operators.length; i++ ) {
				var op = operators[i];
				var selected = '';
				if( op == '>' ) {
					op = '&gt;';
				} else if( op == '<' ) {
					op = '&lt;';
				}
				select += '<option value="' + op + '" ' + selected + '>' +op + '</option>';
			}	
			if( range != undefined && range.type == "list" ){
				for( var v=0 ; v<range.values.length ; v++ ) {
					var txt = (ah.type == "String")? "'" + range.values[v].value + "'": range.values[v].value;
					select += '<option value="= ' + txt + '">= ' + txt + '</option>';
				}
			}
			select += '</select>';
			rootDiv.append(select);

			rootDiv.append('<input type=text id="' + this.rootId 
					+ '_val" class=inputvalue style=\"width:140px; font-size: small;font-family: courier;\" value="' 
					+ default_value + '">');
			if( range != undefined && range.values.length>0 ){
				this.loadRange(valSelector,range);
			}

			rootDiv.append('<input title="units" style="width:100px; margin:2px;" type=text id="' + this.rootId  
					+ '_unit" class=inputvalue style=\"font-size: small;font-family: courier;\" value="' 
					+ supportedUnits[0].text + '">');
			var opSelected = '#' + this.constListId + ' select[id="' + this.rootId + '_op"] option:selected';
			var opInput    = $('#' + this.constListId + ' select[id="' + this.rootId + '_op"]');
			var unitInput  = $('#' + this.constListId + ' input[id="' + this.rootId + '_unit"]');
			var closeInput = $('#' + this.constListId + ' a[id="' + this.rootId + '_close"]');
			var valInput   = $(valSelector);

			var tab = new Array();
			for(var i=0; i<supportedUnits.length; i++){
				tab[tab.length]=supportedUnits[i].text;
			};

			unitInput.autocomplete({
				source: tab,
				minLength: 0,
				focus :function(event, ui) {
					that.fireEnterEvent('AND'
							, $(opSelected).text()
							, valInput.val()
							, ui.item.value);
				}

			});

			$('#' + this.constListId).append("</div>");	

			closeInput.click(function() {
				$(baseSelector).remove();
				that.fireRemoveFirstAndOr(this.rootId);
				that.fireEnterEvent();
				that.fireTypoMsg(false, '');
			});

			opInput.change(function() {
				var v = this.value;
				var regex = /=\s+(.+)/;
				var results = regex.exec(v);
				if( results ) {	
					$('#' + that.constListId + ' select[id="' + that.rootId + '_op"] option[value="="]').prop('selected', true);
					valInput.val(results[1]);
				}
				that.fireEnterEvent('AND'
						, this.value
						, valInput.val()
						, unitInput.val());				
			});
			valInput.keyup(function(event) {
				/*
				 * Run the query is CR is typed in a KW editor
				 */
				if (event.which == '13') {
					if( that.isConstraintOK() ) {
						that.fireRunQuery();
					} else {
						Modalinfo.error("Current contraint is not valid: can not run the query");
					}
				} else {
					that.fireEnterEvent('AND'
							, $(opSelected).text()
							, this.value
							, unitInput.val());
				}
			});
			valInput.click(function(event) {
				that.fireEnterEvent('AND'
						, $(opSelected).text()
						, this.value
						, unitInput.val()
				);
			});
			valInput.on('input', function(event) {
				that.fireEnterEvent('AND'
						, $(opSelected).text()
						, this.value
						, unitInput.val()
				);
			});
			unitInput.keyup(function(event) {
				that.fireEnterEvent('AND'
						, $(opSelected).text()
						, valInput.val()
						, this.value);
			});
			this.fireEnterEvent('AND'
					, $(opSelected).text()
					,valInput.val()
					,unitInput.val());
		}
	}
});

/**
 * Subclass of KWConstraint_mVc modeling a UCD based constraint
 * @param params
 */
function PosConstraint_mVc(params){
	KWConstraint_mVc.call(this, params);
	new KWConstraint_mvC(new PosConstraint_Mvc(params), this);	
};

PosConstraint_mVc.prototype = Object.create(KWConstraint_mVc.prototype, {
	initForm : { 
		value: function(ah, operators ,andors,range,default_value){
			var that = this;
			$('#' + this.constListId).append("<div class='kwConstraint' id=" + this.rootId + " style='float: none;'>");
			var rootDiv = $('#' + this.constListId + ' #' + this.rootId);
			var str = this.fireGetADQL();
			str = (str.length > 48)? (str.substring(0, 47) + "..."): str;
			rootDiv.append('<span id=' + this.rootId + '_name>' +  str + '</span>');

			rootDiv.append('&nbsp;<a id=' + this.rootId + '_close href="javascript:void(0);" class=closekw title="Remove this Constraint"></a>');
			$('#' + this.constListId).append("</div>");	

			$('#' + this.constListId + ' #' +  this.rootId + "_close").click(function() {
				$('#' + that.constListId + ' #' +  that.rootId).remove();
				that.fireRemoveFirstAndOr(this.rootId);
				that.fireEnterEvent();
				that.fireTypoMsg(0, "");				
			});
			this.fireEnterEvent($('#' + this.constListId + ' #' +  this.rootId + "_andor option:selected").text()
					,$('#' + this.constListId + ' #' +  this.rootId + "_op option:selected").text()
					,$('#' + this.constListId + ' #' +  this.rootId + "_val").val());
		}
	}
});

/**
 * Subclass of KWConstraint_mVc modeling a catalogue class based constraint
 * @param params
 */
function CatalogueConstraint_mVc(params){
	KWConstraint_mVc.call(this, params);
	new KWConstraint_mvC(new  CatalogueConstraint_Mvc(params), this);	
};

CatalogueConstraint_mVc.prototype = Object.create(KWConstraint_mVc.prototype, {
	initForm : { 
		value: function(ah, operators ,andors,range,default_value){
			var that = this;
			$('#' + this.constListId).append("<div class='kwConstraint' id=" + this.rootId + " style='float: none;'>");
			var baseSelector  = '#' + this.constListId + ' div[id="' + this.rootId + '"]';
			var rootDiv = $(baseSelector);
			rootDiv.append('&nbsp;<a id="' + this.rootId + '_close" href="javascript:void(0);" class=closekw title="Remove this Constraint"></a>&nbsp;');
			rootDiv.append('<span id="' + this.rootId + '_name">' + ah.ACDS_CATACRO + '</span>&nbsp;<span>at</span>&nbsp;');
			/*
			 * Logical operators
			 */
			if( operators.length > 0 ) {
				var select='<select id="' + this.rootId + '_op" style="font-size: small;font-family: courier;">';
				for( var i=0 ; i<operators.length; i++ ) {
					var op = operators[i];
					var selected = '';
					if( op == '>' ) {
						op = '&gt;';
					} else if( op == '<' ) {
						op = '&lt;';
						selected = 'selected';
					}
					select += '<option value="' + op + '" ' + selected + '>' +op + '</option>';
				}	
				select += '</select>';
				rootDiv.append(select);

				rootDiv.append('<input type=text id="' + this.rootId 
						+ '_val" class=inputvalue style="width:100px; font-size: small;font-family: courier;" value="' 
						+ default_value + '"> <span>arcsec</span>');
			}
			$('#' + this.constListId).append("</div>");	

			var opselected = '#' + this.constListId + ' select[id="' + this.rootId + '_op"] option:selected';
			var opInput    = $('#' + this.constListId + ' select[id="' + this.rootId + '_op"]');
			var valInput   = $('#' + this.constListId + ' input[id="' + this.rootId + '_val"]');
			var closeInput = $('#' + this.constListId + ' a[id="' + this.rootId + '_close"]');

			closeInput.click(function() {
				rootDiv.remove();
				that.fireRemoveFirstAndOr(this.rootId);
				that.fireEnterEvent();
				that.fireTypoMsg(false, '');
			});
			opInput.change(function() {
				that.fireEnterEvent('AND'
						, this.value
						, valInput.val());				
			});
			valInput.keyup(function(event) {
				that.fireEnterEvent('AND'
						, $(opselected).text()
						, this.value);
			});
			valInput.click(function(event) {
				that.fireEnterEvent('AND'
						, $(opselected).text()
						, this.value);
			});
			valInput.on('input', function(event) {
				that.fireEnterEvent('AND'
						, $(opselected).text()
						, this.value);
			});
			this.fireEnterEvent('AND'
					, $(opselected).text()
					,valInput.val());
		}
	}
});

/**
 * Subclass of KWConstraint_mVc modeling a constraint based on catalogue class 
 * with a proba of id 
 * @param params
 */
function CrossidConstraint_mVc(params){
	CatalogueConstraint_mVc.call(this, params);
	new KWConstraint_mvC(new  CrossidConstraint_Mvc(params), this);	
};

CrossidConstraint_mVc.prototype = Object.create(CatalogueConstraint_mVc.prototype, {
	initForm : { 
		value: function(ah, operators ,andors,range,default_value){
			var that = this;
			$('#' + this.constListId).append("<div class='kwConstraint' id=" + this.rootId + " style='float: none;'>");
			var baseSelector  = '#' + this.constListId + ' div[id="' + this.rootId + '"]';
			var rootDiv = $(baseSelector);
			rootDiv.append('&nbsp;<a id="' + this.rootId + '_close" href="javascript:void(0);" class=closekw title="Remove this Constraint"></a>&nbsp;');
			rootDiv.append('<span id="' + this.rootId + '_name">' + ah.ACDS_CATACRO + '</span>&nbsp;<span>identfied with a proba</span>&nbsp;');
			/*
			 * Logical operators
			 */
			if( operators.length > 0 ) {
				var select='<select id="' + this.rootId + '_op" style="font-size: small;font-family: courier;">';
				for( var i=0 ; i<operators.length; i++ ) {
					var op = operators[i];
					var selected = '';
					if( op == '>' ) {
						op = '&gt;';
						selected = 'selected';
					} else if( op == '<' ) {
						op = '&lt;';
					}
					select += '<option value="' + op + '" ' + selected + '>' +op + '</option>';
				}	
				select += '</select>';
				rootDiv.append(select);

				rootDiv.append('<input type=text id="' + this.rootId 
						+ '_val" class=inputvalue style="width: 80px; font-size: small;font-family: courier;" value="' 
						+ default_value + '"> <span>%</span>');
			}
			$('#' + this.constListId).append("</div>");	

			var opselected = '#' + this.constListId + ' select[id="' + this.rootId + '_op"] option:selected';
			var opInput    = $('#' + this.constListId + ' select[id="' + this.rootId + '_op"]');
			var valInput   = $('#' + this.constListId + ' input[id="' + this.rootId + '_val"]');
			var closeInput = $('#' + this.constListId + ' a[id="' + this.rootId + '_close"]');

			closeInput.click(function() {
				rootDiv.remove();
				that.fireRemoveFirstAndOr(this.rootId);
				that.fireEnterEvent();
				that.fireTypoMsg(false, '');
			});
			opInput.change(function() {
				that.fireEnterEvent('AND'
						, this.value
						, valInput.val());				
			});
			valInput.keyup(function(event) {
				that.fireEnterEvent('AND'
						, $(opselected).text()
						, this.value);
			});
			valInput.click(function(event) {
				that.fireEnterEvent('AND'
						, $(opselected).text()
						, this.value);
			});
			valInput.on('input', function(event) {
				that.fireEnterEvent('AND'
						, $(opselected).text()
						, this.value);
			});
			this.fireEnterEvent('AND'
					, $(opselected).text()
					,valInput.val());
		}
	}
});
/**
 * USed by TAP. Just store a trepath in order to used in query covering multiple tables
 * @param params
 * @returns {CatalogueConstraint_mVc}
 */
function TapKWConstraint_mVc(params){
	KWConstraint_mVc.call(this, params);
	new KWConstraint_mvC(new  TapKWConstraint_Mvc(params), this);	
};
TapKWConstraint_mVc.prototype = Object.create(KWConstraint_mVc.prototype, {
});


console.log('=============== >  KWConstraint_v.js ');

KWConstraint_mvC = function(model, view){
	/**
	 * listen to the view
	 */
	var vlist = {
			controlEnterEvent : function(andor, operator, operand, unit){
				model.processEnterEvent(andor, operator, operand, unit);
			},
			controlRemoveConstRef : function(operator, operand){
				model.processRemoveConstRef(operator, operand);
			},
			controlRemoveFirstAndOr: function(key){
				model.processRemoveFirstAndOr(key);
			},
			controlRemoveAndOr: function(key){
				model.removeAndOr(key);
			},
			controlGetADQL: function(attQuoted){
				return model.getADQL(attQuoted);
			}, 
			controlInit: function(){
				return model.notifyInitDone();
			},
			controlTypoMsg: function(fault, msg){
				return model.notifyTypoMsg(fault, msg);
			},
			controlRunQuery: function(){
				return model.notifyRunQuery();
			}
	};
	view.addListener(vlist);

	var mlist = {
			isInit : function(attributehandler, operators ,andors,range, default_value){
				view.initForm(attributehandler, operators ,andors,range,default_value);
			},
			notifyFault: function(fault){
				view.drawFault(fault);
			},
			controlAhName: function(ah){
				return view.getAhName(ah);
			}
	};
	model.addListener(mlist);
};

console.log('=============== >  KWConstraint_c.js ');

/**
 * 
 * @param params
 *  - parentDivId: id of the div where the view is drawn
 *  - queryView:  widget managing the global quey: invoked at each change on the view 
 *  - formName   : Name of the current form
 *  - title : Title of the field set
 *  - products : [{relation, label}]: array of object attached to each checkbox
 *         - relation: name of the relationship pointing on the attached product
 *         - label: label to display by the check box
 * @returns
 */
function AttachedData_mVc(params){
	this.queryView   = params.queryView;
	this.parentDivId = params.parentDivId;
	this.title       = params.title;
	this.formName    = params.formName;
	this.products    = params.products;

	this.productListId = this.formName + "_ProductList";
};


/**
 * Methods prototypes
 */
AttachedData_mVc.prototype = {
		/**
		 * Draw the field container
		 */
		draw : function() {
			var that = this;
			var html = '<fieldset style="display inline-block; width: 100%;" >\n'
				+ '  <legend>' + this.title + '</legend>\n'
				+ '    <div id="' + this.productListId + '" style="display: inline; float: left; width: 50%">\n'
				+ '	   </div>\n'
				+ '	   <div style="display: inline; float: left; margin-left: 30px;"class="spanhelp">\n'
				+ '		one click on the check button for with,<br> two click on the\n'
				+ '		check button for without,<br> three click on the check button\n'
				+ '		for nothing ...\n'
				+ '	   </div>\n'
				+ '</fieldset>\n';
			$('#' + this.parentDivId).html(html);
			for( var i=0 ;  i<this.products.length ; i++ ) {
				var html = '<div id=' + this.products[i].relation + ' style="width: 100%;display: block; float: left">\n'
				+ '  <input id=' + this.products[i].relation + ' class="anydata" type="button" value="0" />\n'
				+ '  <span id=' + this.products[i].relation + ' class="tt">Whatever</span>\n'
				+ '  <span class="tt">' + this.products[i].label + '</span>\n'
				+ '</div><br>\n';
				$('#' + this.productListId).append(html);

			}

			$('#' + this.productListId + ' input').click(function(element) {
				var val = $(this).val();
				var id = $(this).attr('id');
				if( val == 0){
					$(this).attr('class','withdata').attr('value',1);
					$('.tt#'+id).text("With");
				}
				else if(val == 1){
					$(this).attr('class','withoutdata').attr('value',2);
					$('.tt#'+id).text("Without");
				}
				else if(val == 2){
					$(this).attr('class','anydata').attr('value',0);
					$('.tt#'+id).text("Whatever");
				}	
				that.updateQuery();
			});
		},

		updateQuery: function(element){
			var pattern = "";		
			var nl = "";
			$("#" + this.productListId + " input").each(function(){
				var val = $(this).attr("class");
				var produit = $(this).attr("id");
				if( val == "withdata") {
					pattern += nl + '    matchPattern{ '+produit +'}';
					nl = "\n";
				} else if( val == "withoutdata") {
					pattern += nl + '    matchPattern{ '+produit +', Cardinality = 0}';
					nl = "\n";
				}
			});
			if( this.queryView != null )
				this.queryView.fireAddConstraint(this.formName, "relation", pattern);
			else Out.info("Add pattern " + pattern + " no query view");
		},
		fireClearAllConst: function() {
			$('#' + this.productListId + " input").attr('class','anydata').attr('value',0);
			$('#' + this.productListId + ' .tt[id]').text("Whatever");
			this.updateQuery();
		}
};
console.log('=============== >  AttachedData_v.js ');

/**
 * 
 * @param params
 *  - parentDivId: id of the div where the view is drawn
 *  - queryView:  widget managing the global quey: invoked at each change on the view 
 *  - formName   : Name of the current form
 *  - title : Title of the field set
 *  - getMeta : Url returning an object formated like [{setname, keywords: []}...]: 
 *         - setName: name of the keyword set (mission, waveband...)
 *         - keywords: set of keywords
 * @returns
 */
function VizierKeywords_mVc(params){
	this.queryView   = params.queryView;
	this.parentDivId = params.parentDivId;
	this.title       = params.title;
	this.formName    = params.formName;
	this.getMetaUrl     = params.getMetaUrl;

	this.productListId = this.formName + "_ProductList";
	this.testData = [
	                 {setName: "set1", keywords: ["set1_kw1", "set1_kw2", "set1_kw3"]},
	                 {setName: "set2", keywords: ["set2_kw1", "set2_kw2", "set2_kw3"]},
	                 {setName: "set3", keywords: ["set3_kw1", "set1_kw2", "set3_kw3"]}
	                 ];
};
/**
 * Methods prototypes
 */
VizierKeywords_mVc.prototype = {
		/**
		 * Draw the field container
		 */
		draw : function() {
			var that = this;
			if( this.getMetaUrl != null ) {
				$.getJSON(this.getMetaUrl,function(data) {
					that.drawKeywordsSelectors(data);
				});
			} else {
				this.drawKeywordsSelectors(this.testData);
			}
		},
		drawKeywordsSelectors : function(data) {
			var that = this;
			var html = '<div style="display inline-block; width: 100%;" >\n';
				//+ '  <legend>Vizier Keyword Selector</legend>\n';

			for( var set=0 ; set<data.length ; set++ ) {
				var name = data[set].setName;
				var kws  = data[set].keywords;
				html += '<fieldset  style="float: left;">\n'
					  + '  <legend>' + name + '&nbsp;<a name="' + name + '" class=closekw  href="javascript:void(0);" title="Unselect all items"></a></legend>\n';
				
				html += '<select name="' + name + '"  style=\"background-color: white;\" multiple="multiple" size="7" width="100%">';
				for ( var i=0; i<kws.length; i++){
					html += '<option title="' + name + '" value="'+kws[i]+'">'+kws[i]+'</option>';
				}
				html += '</select>\n</fieldset>\n';
			}				
			html += 
				'	   <div style="width: 270px;display: inline; float: left; margin-left: 10px;"class="spanhelp">\n'
			  + '		- The keywords proposed here are the same as those used by Vizier to tag catalogues<br>\n'
			  + '		- Constraint on keywords filter X-ray sources correlated with at least one source of a catalogue \n'
			  + '		matching all the selected keywords<br>\n'
			  + '		- Shift click to achieve multiple selections<br>\n'
			  + '       - The constraint on one keyword can be reversed by a ! set before the keyword in the text of the query'
			  + '	   </div>\n';

			html += '</div>';
			$('#' + this.parentDivId).append(html);
			
			$("#" + this.parentDivId + " select").change(function () {
				that.updateQuery();
			});
			$("#" + this.parentDivId + " a.closekw").click(function () {
				$("#" + that.parentDivId + ' option[title="' + this.name + '"]').removeAttr("selected");
				that.updateQuery();
			});


		},		
		updateQuery: function(){
			var selected = new Array();
			$("#" + this.parentDivId + " option:selected").each(function () {
				var kws = selected[this.title];
				if( kws == null ) kws = selected [this.title] = new Array();
				kws.push(this.value);
			});
			var retour = new Array();
			for( k in selected) {
				retour.push('    "' + k + '=' + selected[k].join(",") + '"');
			}
			this.queryView.fireAddConstraint(this.formName, "kwconst", retour.join("\n"));
		},
		fireClearAllConst: function() {
			$("#" + this.parentDivId + ' option').removeAttr("selected");
			this.updateQuery();			
		}
		
};

console.log('=============== >  VizierKeywords_v.js ');

/**
 * 
 * @param parentDivId: ID of the div containing thge filed list. It must exist before.
 * @param formName    : Name of the form. Although internal use must be 
 *                      set from outside to avoi conflict by JQuery selectors  
 * @param  constContainerId  : Id of the div containing all the list: must existe before             
 * @param  orderByHandler   : Handler reset commans         
 */
OrderBy_mVc = function(parentDivId, formName, constContainerId, orderByHandler){
	var orderByDesId  = parentDivId + "_orderdesc";
	var orderByAscId  = parentDivId + "_orderasc";
	var orderByDrop   = parentDivId + "_orderdrop";
	var orderById     = parentDivId + "_orderby";
	/**
	 *  parentDiv: JQuery DOM node of the container
	 */
	this.draw = function() {
		$('#' + constContainerId).append('<div class=orderby>'
				+ '<input id=' + orderById 
				+ ' class="orderby" type="text" value="Order By" disabled="disabled">'
				+ '<label> desc <input id=' + orderByDesId + ' type="radio" name="OrderBy" value="desc"> </label>'
				+ '<label>asc <input id=' + orderByAscId + ' type="radio" name="OrderBy" value="asc" checked> </label>'	
				+ '<a href="javascript:void(0);" id=' + orderByDrop + ' class=closekw title="Reset OrderBy"></a>'			
				+ '</div>');
		
		$('#' + orderByDrop).click(function() {
			$('.orderby') .val('Order By');
			$('.orderby') .css('font-style' , 'italic');
			$('.orderby') .css('color' , 'darkgray');
			orderByHandler('OrderBy');
		});
		$('#' + orderByAscId).click(function() {
			orderByHandler($('#' + orderById).val());
		});
		$('#' + orderByDesId).click(function() {
			orderByHandler($('#' + orderById).val());
		});
	};
	this.setOrderBy = function(attname) {
		$('.orderby') .css('font-style' , '');
		$('.orderby') .css('color' , 'black');
		$('.orderby').val(attname);
	};
	this.getOrderBy = function (){
		return $('#' + orderById).val();
	};
	this.isDesc = function() {
		return ($('#' + orderByDesId).attr("checked"))?true: false;
	};
	this.fireClearAllConst= function (){
		$('.orderby').val('');
		$('#' + orderByDesId).attr("checked", "true");
	};	
	
};


console.log('=============== >  OrderBy_v.js ');

/**
 * COne Search form View
 * 
 * @param params: JS object with the following fields
 * 	parentDivId: ID of the parent div
 *  formName   : Name of the current form
 *  frames     : Arrays of the supported frames
 *  urls   : JS object containing handlers processing events:
 *        sesameURL : name resolver
 *        uploadURL : Handle the upload of position lists
 */
function ConeSearch_mVc(params){
	this.editor = params.editor;
	this.parentDivId = params.parentDivId;
	this.formName = params.formName;
	this.frames = params.frames,
	this.sesameURL = params.urls.sesameURL;
	this.uploadURL = params.urls.uploadURL;
	this.cooFieldId = this.formName + "_CScoofield";
	this.radiusFieldId = this.formName + "_CSradiusfield";
	this.frameSelectId = this.formName + "_CSframeselect";
	this.uploadId = this.formName + "_CSupload";
	this.sesameId = this.formName + "_CSsesame";
	this.stackId = this.formName + "_CSstack";
	this.regionId = this.formName + "_CSregion";
};
/**
 * Methods prototypes
 */
ConeSearch_mVc.prototype = {
		/**
		 * Draw the field container
		 */
		draw : function() {
			if( this.frames == null || this.frames .length == 0 ) {
				this.frames = ['ICRS'];
			}
			var html = '<fieldset class=fieldiv style="display: inline; float: left;height: 130px;">'
				+ '  <legend> Cone Search Setup </legend>'
				+ '     <div style="background: transparent;">'
				+ '       <span style="text-align: right; display: inline-block; width: 7em;">Coord/Name</span>'
				+ '       <input type=text id="' + this.cooFieldId + '" class=inputvalue  size=18 />'
				+ '       <a href="javascript:void(0);" id="' + this.sesameId + '" title="Invoke the CDS name resolver" class=sesame></a>'
				+ '       <br>'
				+ '       <span style="text-align: right; display: inline-block; width: 7em;">Radius(arcmin)</span>'
				+ '       <input type=text id="' + this.radiusFieldId + '" class=inputvalue style="width: 135px;" value="1" />'
				+ '      <br>'
				+ '      <span style="text-align: right; display: inline-block; width: 7em;">System</span>'
				+ '      <select id="' + this.frameSelectId + '" >'
				+ '      </select>'
				+ '      <br><span id=' + this.regionId + ' class="datafield activehover">Draw a Search Region</span> <br>'
				+ '      <input type=button id="' + this.uploadId + '" value="Upload Position List"/>'
				+ '      <div style="display: inline; float: right;"><span class=help id="uploadresult"></span></div>'
				+ '    </div>'
				+ '</fieldset>';
			$('#' + this.parentDivId).append(html);
			var s = $('#' + this.frameSelectId);
			for( var i=0 ; i<this.frames.length ; i++  ) {
				s.append('<option value=' + this.frames[i] + '>' +this.frames[i] + '</option>');
			}
			this.setUploadForm();
			this.setSesameForm();
		},
		setRegionForm: function(handler) {
			var that = this;
			$('#' + this.regionId). click(function() {
				var dv = null;
				if( that.editor ){
					dv = that.editor.getDefaultValue();
				}
				Modalinfo.region(handler
						, dv
//						,  [84.24901652054093, -5.640882748140112,83.34451837951998, -6.103216341255678,83.60897420186223, -4.553808802262613]
				);});
		},
		setUploadForm: function() {
			var that = this;
			var handler = ( this.uploadURL != null )
			? function() {Modalinfo.uploadForm("Upload a list of position"
					, that.uploadURL
					, "Upload a CSV position list<br>Error are in arcmin"
					, function(returnedPath){				
						var msg;
						if( returnedPath.retour.name != undefined && returnedPath.retour.size != undefined ) {
							msg = " File " + returnedPath.retour.name  + ' uploaded<br>' + returnedPath.retour.positions + ' positions';
						} else {
							msg = JSON.stringify(returnedPath.retour);
						}
						$('span#uploadresult').html(msg);
						that.editor.firePoslistUpload(returnedPath.path.filename);
						Modalinfo.close();
					}
					, function(){$('span#uploadresult').text('');});
			} 
			: function(){Modalinfo.info("Upload not implemented yet");};
			$('#' + this.uploadId).click(handler) ;

		},
		setSesameForm: function() {
			var inputfield = $('#' + this.cooFieldId);
			var handler = ( this.sesameURL != null )
			? function() {			
				Processing.show("Waiting on SESAME response");
				$.getJSON("sesame", {object: inputfield.val() }, function(data) {
					Processing.hide();
					if( Processing.jsonError(data, "Sesame failure") ) {
						return;
					} else {
						inputfield.val(data.alpha + ' ' + data.delta);
					}
				});}
			:  function(){Modalinfo.info("name resolver not implemented yet");};

			$('#' + this.sesameId).click(handler) ;
		},
		hasSearchParameters: function() {
			var coo = $('#' + this.cooFieldId).val();
			var radius= $('#' + this.radiusFieldId).val();
			if( coo.trim() == "" || isNaN(radius) ) {
				return false;				
			} else {
				return true;
			}
		},
		getSearchParameters: function() {
			var coo = $('#' + this.cooFieldId).val();
			var radius= $('#' + this.radiusFieldId).val();
			var frame = $('#' + this.frameSelectId + '  option:selected').text();
			if( coo.trim() == "" ) {
				Modalinfo.error("No coordinates given");
				return null;
			}else if( isNaN(radius) ) {
				Modalinfo.error("Radius field requires a numerical value");
				return null;				
			} else {
				return {type: "cone", position: coo, radius: radius, frame: frame};
			}
		},
		resetPosition: function(){
			$('#' + this.cooFieldId).val("");
		}
};


/**
 * Used for merged catalogues 3XMM
 * @param params
 * @returns {SimplePos_mVc}
 */
function SimplePos_mVc(params){
	ConeSearch_mVc.call(this, params);
	this.queryView = params.queryView;
};

/**
 * Method overloading
 */
SimplePos_mVc.prototype = Object.create(ConeSearch_mVc.prototype, {
	/**
	 * Draw the field container
	 */
	draw : {
		value: function() {
			var that = this;
			if( this.frames == null || this.frames .length == 0 ) {
				this.frames = ['ICRS'];
			}
			var html = '<div style="background: transparent;  float: left;">'
				+ '       <span style="text-align: right; display: inline-block; width: 7em;">Coord/Name</span>'
				+ '       <input type=text id="' + this.cooFieldId + '" class=inputvalue  size=18 />'
				+ '       <a href="javascript:void(0);" id="' + this.sesameId + '" title="Invoke the CDS name resolver" class=sesame></a>'
				+ '       <span>Radius(arcmin)</span>'
				+ '       <input type=text id="' + this.radiusFieldId + '" class=inputvalue style="width: 40px;" value="1" />'
				+ '      <span>System</span>'
				+ ((this.frames != null)? '      <select id="' + this.frameSelectId + '" >':'')
				+ '      </select>'
				+ '      <input type=button id="' + this.uploadId + '" value="Upload Position List"   />'
				+ '      <div style="width: 110px; height: 30px; float: right; display: inline; overflow: hidden;">'
				+ '         <span class=help id="uploadresult"></span>'
				+ '      </div>'
				+ '    </div>';
			$('#' + this.parentDivId).append(html);
			var s = $('#' + this.frameSelectId);
			for( var i=0 ; i<this.frames.length ; i++  ) {
				s.append('<option value=' + this.frames[i] + '>' +this.frames[i] + '</option>');
			}
			this.setUploadForm();
			this.setSesameForm();

			$('#' + this.cooFieldId ).keyup(function(event) {
				if ( event.which == 13 ) {
					event.preventDefault();
				} else{
					that.readAndUpdate();
				}
			});
			$('#' + this.cooFieldId ).click(function(event) {
				that.readAndUpdate();
			});
			$('#' + this.radiusFieldId ).keyup(function(event) {
				if ( event.which == 13 ) {
					event.preventDefault();
				} else{
					that.readAndUpdate();
				}
			});
			$('#' + this.radiusFieldId ).click(function(event) {
				that.readAndUpdate();
			});
			$('#' + this.frameSelectId ).change(function(event) {
				that.readAndUpdate();
			});
		}
	},	
	readAndUpdate:{
		value: function() {
			var coo = $('#' + this.cooFieldId).val();
			var radius= $('#' + this.radiusFieldId).val();
			var frame = $('#' + this.frameSelectId + '  option:selected').text();
			this.updateQuery(coo, radius, frame);
		}
	},
	setUploadForm : {
		value : function() {
			var that = this;
			var handler = ( this.uploadURL != null )
			? function() {Modalinfo.uploadForm("Upload a list of position"
					, that.uploadURL
					, "Upload a CSV position list<br>Error are in arcmin"
					, function(returnedPath){				
						var msg;
						if( returnedPath.retour.name != undefined && returnedPath.retour.size != undefined ) {
							msg = returnedPath.retour.name  + ' uploaded, ' + returnedPath.retour.positions + ' positions';
						} else {
							msg = JSON.stringify(returnedPath.retour);
						}
						$('#' + that.parentDivId + ' span#uploadresult').html(msg);
						$('#' + that.cooFieldId).val("poslist:" + returnedPath.retour.name);
						$('#' + that.radiusFieldId).val("0");
						that.readAndUpdate();
					}
					, function(){$('span#uploadresult').text('');});
			} 
			: function(){Modalinfo.info("Upload not implemented yet");};
			$('#' + this.uploadId).click(handler) ;

		}
	},
	setSesameForm : {
		value : function() {
			var that = this;
			var inputfield = $('#' + this.cooFieldId);
			var handler = ( this.sesameURL != null )
			? function() {			
				Processing.show("Waiting on SESAME response");
				$.getJSON("sesame", {object: inputfield.val() }, function(data) {
					Processing.hide();
					if( Processing.jsonError(data, "Sesame failure") ) {
						that.updateQuery('', '', null);
						return;
					} else {
						inputfield.val(data.alpha + ' ' + data.delta);
						that.readAndUpdate();
					}
				});}
			:  function(){Modalinfo.info("name resolver not implemented yet");};

			$('#' + this.sesameId).click(handler) ;
		}
	},
	updateQuery : {
		value: function(coord, radius, frame) {
			if( this.queryView != null ) {
				this.queryView.fireDelConstraint(this.formName, "position");
				if( coord != '' && radius != '' ){
					this.queryView.fireAddConstraint(this.formName
							, "position"
							, '    isInCircle("' + coord + '", ' + radius + ', ' 
							+ ((frame == "FK5")? "J2000":
								(frame == "FK4")? "J1950": '-')
								+ ', ' + frame + ')');
				}
			} else {
				Out.info("No query view");
			}
		}
	},
	fireClearAllConst: {
		value: function() {
			var that = this;
			$('#' + this.cooFieldId).val('');
			that.readAndUpdate();
		}
	}
});


/**
 * Used for Taphandle
 * params.postUploadHandler is invoked after the upload succeed
 * It recieved the an object as parameter {name: filename, size: filesize, positions: num of valid positions}
 * @param params
 * @returns {SimplePos_mVc}
 */
function TapSimplePos_mVc(params){
	SimplePos_mVc.call(this, params);
	this.handler = this.editor.fireInputCoordEvent;		
	this.postUploadHandler= params.postUploadHandler;
	this.uploadedFile = ""; 
};

/**
 * Method overloading
 */
TapSimplePos_mVc.prototype = Object.create(SimplePos_mVc.prototype, {
	/**
	 * Draw the field container
	 */
	draw : {
		value: function() {
			var that = this;
			if( this.frames == null || this.frames .length == 0 ) {
				this.frames = ['ICRS'];
			}
			var html = '<div style="background: transparent; position: absolute; top: 0px; left: 0px; width: 460px; height: 55px">'
				+ '       <span class=help style="text-align: right; display: inline-block; width: 7em;">Coord/Name</span>'
				+ '       <input type=text id="' + this.cooFieldId + '" class=inputvalue  size=18 />'
				+ '       <a href="javascript:void(0);" id="' + this.sesameId + '" title="Invoke the CDS name resolver" class=sesame></a>'
				+ '       <span class=help >System</span>'
				+ ((this.frames != null)? '      <select id="' + this.frameSelectId + '" >':'')
				+ '      </select>'
				+ '       <br><span class=help >Radius(arcmin)</span>'
				+ '       <input type=text id="' + this.radiusFieldId + '" class=inputvalue style="width: 40px;" value="1" />'
				+ '       <input class=stackconstbutton id="' + this.stackId + '" type="button"/>'
				+ '       <input type=button id="' + this.uploadId + '" value="Upload Position List" disabled title="Not implemented yet (wait for next release)"/>'
				+ '       <div style="width: 150px; height: 30px; position: absolute; top: 25px; left: 295px; overflow: hidden; background-color: whitesmoke;">'
				+ '            <span class=help id="uploadresult"></span>'
				+ '       </div>'
				+ ' </div>';
			$('#' + this.parentDivId).append(html);
			var s = $('#' + this.frameSelectId);
			for( var i=0 ; i<this.frames.length ; i++  ) {
				s.append('<option value=' + this.frames[i] + '>' +this.frames[i] + '</option>');
			}
			this.setUploadForm();
			this.setSesameForm();
			$('#' + this.stackId ).click(function() {
				that.readAndUpdate();				
			});
			$('#' + this.cooFieldId ).keyup(function(event) {
				if ( event.which == 13 ) {
					that.readAndUpdate();
				}
			});
//			$('#' + this.cooFieldId ).click(function(eventObject) {
//			that.readAndUpdate();
//			});
			$('#' + this.radiusFieldId ).keyup(function(event) {
				if ( event.which == 13 ) {
					that.readAndUpdate();
				}
			});
//			$('#' + this.radiusFieldId ).click(function(eventObject) {
//			that.readAndUpdate();
//			});
			$('#' + this.frameSelectId ).change(function(event) {
				that.readAndUpdate();
			});
		}
	},	
	readAndUpdate:{
		value: function() {
			this.uploadedFile = "";
			var coo = $('#' + this.cooFieldId).val();
			var radius= $('#' + this.radiusFieldId).val();
			var frame = $('#' + this.frameSelectId + '  option:selected').text();
			if( coo.length == 0 ) {
				Modalinfo.error("No coordinates given", "input error");
			} else if( radius.length == 0 ) {
				Modalinfo.error("No radius given", "input error");
			} else if( !$.isNumeric(radius)  ) {
				Modalinfo.error("Radius must be numeric", "input error");
			} else {
				var rd = coo.split(/\s+/);
				if( coo.startsWith('poslist:')  ) {
					this.uploadedFile = coo.replace('poslist:','');
					this.editor.fireInputCoordEvent(coo, null, radius, frame);				
				} else if( rd.length != 2 ) {
					Modalinfo.error("Coordinates must be separataed with a blank", "input error");					
				} else if( !$.isNumeric(rd[0]) ||!$.isNumeric(rd[1])  ) {
					Modalinfo.error("Radius must be numeric", "input error");
				} else {					
					this.editor.fireInputCoordEvent(rd[0], rd[1], radius, frame);
				}
			}
		}
	},
	setUploadForm : {
		value : function() {
			var that = this;
			var handler = ( this.uploadURL != null )
			? function() {
				var radius= $('#' + that.radiusFieldId).val();
				if( radius == "" ){
					Modalinfo.error("Radius must be set");
				} else	if( that.editor.isReadyToUpload() ) {
					Modalinfo.uploadForm("Upload a list of position"
							, that.uploadURL
							, "Upload a CSV position list (ra dec or object name)"
							, function(returnedPath){				
								var msg;
								if( returnedPath.retour.name != undefined && returnedPath.retour.positions != undefined ) {
									msg = returnedPath.retour.name  + ' uploaded, ' + returnedPath.retour.positions + ' positions';
									$('#' + that.cooFieldId).val("poslist:" + returnedPath.retour.name);
									$('#' + that.parentDivId + ' span#uploadresult').html(msg);
									that.readAndUpdate();
									if( that.postUploadHandler !=null ) {
										that.postUploadHandler( returnedPath.retour);
									}
									Modalinfo.close();
								} else {
									Modalinfo.error(JSON.stringify(returnedPath), "Upload Failure");
								}
							}
							, function(){$('span#uploadresult').text('');}
							, [{name: 'radius', value: radius}]);
				}
			} 
			: function(){Modalinfo.info("Upload not implemented yet");};
			$('#' + this.uploadId).click(handler) ;

		}
	},
	setSesameForm : {
		value : function() {
			var that = this;
			var inputfield = $('#' + this.cooFieldId);
			var handler = ( this.sesameURL != null )
			? function() {			
				Processing.show("Waiting on SESAME response");
				$.getJSON("sesame", {object: inputfield.val() }, function(data) {
					Processing.hide();
					if( Processing.jsonError(data, "Sesame failure") ) {
						that.updateQuery('', '', null);
						return;
					} else {
						inputfield.val(data.alpha + ' ' + data.delta);
						that.readAndUpdate();
					}
				});}
			:  function(){Modalinfo.info("name resolver not implemented yet");};

			$('#' + this.sesameId).click(handler) ;
		}
	},
	updateQuery : {
		value: function(coord, radius, frame) {
			if( this.queryView != null ) {
				this.queryView.fireDelConstraint(this.formName, "position");
				if( coord != '' && radius != '' ){
					this.queryView.fireAddConstraint(this.formName
							, "position"
							, '    isInCircle("' + coord + '", ' + radius + ', ' 
							+ ((frame == "FK5")? "J2000":
								(frame == "FK4")? "J1950": '-')
								+ ', ' + frame + ')');
				}
			} else {
				Out.info("No query view");
			}
		}
	},
	fireClearAllConst: {
		value: function() {
			$('#' + this.cooFieldId).val('');
			that.readAndUpdate();
		}
	}
});

console.log('=============== >  ConeSearch_v.js ');

/**
 * 
 * @param parentDivId: ID of the div containing thge filed list. It must exist before.
 * @param formName    : Name of the form. Although internal use must be 
 *                      set from outside to avoi conflict by JQuery selectors  
 * @param  constContainerId  : Id of the div containing all the list: must existe before             
 * @param  removeAllHandler   : Handler processing the click on the remove all button          
 */
ConstList_mVc = function(parentDivId, formName, constContainerId, removeAllHandler){
	/**
	 * keep a reference to ourselves
	 */
	var that = this;

	var constListId   = parentDivId + "_constlist";
	var delConstListId   = parentDivId + "_delconstlist";
	var typoMsgId     = parentDivId + "_typoMsgId";
	/**
	 *  parentDiv: JQuery DOM node of the container
	 */
	this.draw = function() {
//		$("#" + constContainerId).append('<div class=constdiv ><fieldset class="constraintlist" id="' + constListId	+  '">'
//				+ '<legend class=help>List of Active Constraints <a href="javascript:void(0);" id=' + delConstListId + ' class=closekw title="Remove all constraints"></a></legend>'
//				+ '<span class=help>Click on a <input class="stackconstbutton" type="button"> button to append,<br>the constraint to the list</span>'
//				+ '</fieldset>'
//				+ '<span style="font-style: italic;color: lightgray;">QL stmt </span><span style="height: 18;" class=typomsg_ok id=' + typoMsgId + '></span>'
//				+ '</div>');
		$("#" + constContainerId).append(
				
				
				  '<div class=constdiv class="constraintlist" style="width: 100%">'
				+ '    <fieldset style="height: 100px; background-color: whitesmoke;">'
				+ '        <legend class=help>List of Active Constraints <a href="javascript:void(0);" id=' + delConstListId + ' class=closekw style="float: none;" title="Remove all constraints"></a></legend>'
				+ '        <div  style="overflow: auto;height: 90px; background-color: transparent; width: 100%" id="' + constListId	+  '"><span class=help>Click on a <input class="stackconstbutton" type="button"> button to append,<br>the constraint to the list</span></div>'

				//+ '        <span class=help>Click on a <input class="stackconstbutton" type="button"> button to append,<br>the constraint to the list</span>'
				+ '    </fieldset>'
				+ '    <div>'
				+ '      <span style="font-style: italic;color: lightgray;">QL stmt </span><span style="height: 18;" class=typomsg_ok id=' + typoMsgId + '></span>'
				+ '    </div>'
				+ '</div>');
		
		$('#' + delConstListId).click(function() {
			removeAllHandler();
		});		
		return constListId;
	};
	
	this.printTypoMsg= function(fault, msg){
		$("#"+ typoMsgId).each(function() {
			if(fault) {
				$(this).attr('class', 'typomsg_ko');
			} else {
				$(this).attr('class', 'typomsg_ok');					
			}
			$(this).text(msg);
		});
	};
	this.isTypoGreen= function(){
		return ( $("#"+ typoMsgId).first().attr('class') ==  'typomsg_ok');					
	};
	this.fireClearAllConst= function() {
		$("#" + constListId + " div.kwConstraint").each(function() {
			$(this).remove();
		});
	};
	this.fireClearConst= function(filter) {
		$("#" + constListId + " div.kwConstraint").each(function() {
			if( $(this).attr("id").match(filter) ) $(this).remove();
		});
	};
	this.fireRemoveAllHandler= function() {
		removeAllHandler();
	};
};

console.log('=============== >  ConstList_v.js ');

/**
 * 
 * @param parentDivId: ID of the div containing thge filed list. It must exist before.
 * @param formName    : Name of the form. Although internal use must be 
 *                      set from outside to avoi conflict by JQuery selectors  
 * @param  handlers       : Object with the handler to be implemented. Possible Fields are                 
 *         stackHandler   : Handler processing the click on the stack button attached to each field            
 *         orderByHandler : Handler processing the click on the Orderby button attached to each field   
 *         raHandler      : Handler processing the click on the RA button   
 *        decHandler      : Handler processing the click on the DEC button   
 */

/**
 * class prototype
 */
function FieldList_mVc(parentDivId, formName, handlers){
	/*
	 * Some reference and IDs on useful  DOM elements
	 */
	this.parentDiv = $("#" +parentDivId );
	this.fieldListId   = parentDivId + "_fieldlist";
	this.fieldTableId   = parentDivId + "_fieldtable";
	this.fieldFilterId = parentDivId + "_fieldfilter";
	this.attributesHandlers = new Array();
	this.filterPattern=null;
	this.formName = formName;
	this.dataTreePath = null;	/// instance of DataTreePath		
	/*
	 * Keep handler references
	 */
	this.stackHandler   = handlers.stackHandler;
	this.orderByHandler = handlers.orderByHandler;
	this.raHandler      = handlers.raHandler;
	this.decHandler     = handlers.decHandler;

	this.stackTooltip = "Click to constrain this field";
}
/**
 * Methods prototypes
 */
FieldList_mVc.prototype = {
		/**
		 * Draw the field container
		 */
		draw : function() {
			var that = this;
			this.attributesHandlers = new Array();
			this.parentDiv.html('<div class=fielddiv><div class="fieldlist" id="' + this.fieldListId
					+  '"></div><input id="' 
					+ this.fieldFilterId
					+  '" class="fieldfilter" type="text" value="Kw Filter" size=15 style="font-style: italic; color: LightGray"/></div>');
			$('#' + this.fieldFilterId).keyup(function() {
				that.filterPattern = new RegExp($(this).val(), 'i');
				that.fireFilter();
			});
			$('#' + this.fieldFilterId).one("click",function() {
				$(this).css('color','black');
				$(this).css('font-style','');
				$(this).attr('value','');
			});
		}	,
		setStackTooltip: function(stackTooltip) {
			this.stackTooltip = stackTooltip;
		},
		setDataTreePath: function(dataTreePath){
			this.dataTreePath = dataTreePath;
			this.displayFields();
		},
		getAttributeTitle : function(ah) {
			return ah.nameorg 
			+ " - database name; " +  ah.nameattr
			+ " - description: " +  ah.comment
			+ " - UCD: " +  ah.ucd
			+ " - Unit: " +  ah.unit
			+ " - Type: " +  ah.type
			+ " - Range: " +  ((ah.range == null || ah.range.values == null)? 'Not Set':JSON.stringify( ah.range.values).replace(/'/g,"&#39;"))
			;
		},
		/**
		 * Draw one field in the container
		 * Field described by the attribute handler ah
		 */
		displayField:  function(ah){
			var that = this;
			var id = this.formName + "_" + ah.nameattr;
			var title = this.getAttributeTitle(ah);
			var row ="<tr class=attlist id=" + ah.nameattr + ">" 
			+"<td class=attlist><span title='" + title + "'>"+ ah.nameorg+"</span></td>"
			+"<td class='attlist help'>" + ah.type +"</td>"
			+"<td class='attlist help'>" + ah.unit +"</td>"
			;
			if( this.orderByHandler != null ) {
				row += "<td class='attlist attlistcmd'>"
					+"<input id=order_" + id + " title=\"Click to order the query result by this field\" class=\"orderbybutton\" type=\"button\" ></input>"
					+"</td>";
			}
			if( this.stackHandler != null ) {
				row += "<td class='attlist attlistcmd'>"
					+"<input id=stack_" + id + " title=\"" + this.stackTooltip  + "\"  class=\"stackconstbutton\" type=\"button\"></input>"
					+"</td>";
			}
			if( this.raHandler != null ) {
				row += "<td class='attlist attlistcmd'>"
					+"<input id=tora_" + id + " title=\"Click to use this field as RA coordinate\"  class=\"raconstbutton\" type=\"button\"></input>"
					+"</td>";
			}
			if( this.decHandler != null ) {
				row += "<td class='attlist attlistcmd'>"
					+"<input id=todec_" + id + " title=\"Click to use this field as DEC coordinate\"  class=\"decconstbutton\" type=\"button\"></input>"
					+"</td>";
			}
			row += "</tr>";
			$('#' + this.fieldTableId).append(row);
			var id = this.formName + "_" + ah.nameattr;
			if( this.orderByHandler != null ) {
				$('#' + this.fieldListId + ' input[id="order_' + id + '"]' ).click(function() {that.orderByHandler($(this).closest("tr").attr("id"));});
			}
			if( this.stackHandler != null ){
				$('#' + this.fieldListId + ' input[id="stack_' + id + '"]' ).click(function() {that.stackHandler($(this).closest("tr").attr("id"));});
			}
			if( this.raHandler != null ){
				$('#' + this.fieldListId + ' input[id="tora_' + id + '"]' ).click(function() {that.raHandler($(this).closest("tr").attr("id"));});
			}
			if( this.decHandler != null ){
				$('#' + this.fieldListId + ' input[id="todec_' + id + '"]' ).click(function() {that.decHandler($(this).closest("tr").attr("id"));});
			}
			$('#' + this.fieldTableId + " tr#" + ah.nameattr + " span").tooltip( {
				track: true,
				delay: 0,
				showURL: false,
				opacity: 1,
				fixPNG: true,
				showBody: " - ",
				// extraClass: "pretty fancy",
				top: -15,
				left: 5
			});
		},

		/**
		 * Draw all fields in the container
		 * Fields are described by the attribute handler array ahs
		 * Warning ahs is  not a map but an array 
		 */
		displayFields : function(){
			var that = this;
			this.attributesHandlers = new Array();
			if( this.dataTreePath != null ) {
				MetadataSource.getTableAtt(
						this.dataTreePath
						, function() {
							var ahm = MetadataSource.ahMap(that.dataTreePath);
							var table  = "<table id=" + that.fieldTableId + " style='width: 100%; border-spacing: 0px; border-collapse:collapse'></table>";
							$('#' + that.fieldListId).html(table);
							for( var k=0 ; k<ahm.length ; k++) {
								var ah = ahm[k];
								that.attributesHandlers[ah.nameattr] = ah;				
								that.addPresetValues(ah);
								that.displayField(ah);
							}
						});
			}
		},
		addPresetValues : function(attributeHandler){
			if( attributeHandler.nameattr == 'dataproduct_type' ) {
				attributeHandler.range = {type: 'list', values: ["'image'", "'spectrum'", "'cube'",
				                                                 "'timeseries'", "'visibility'", "'eventlist'"]};
			} else 	if( attributeHandler.nameattr == 'calib_level' ) {
				attributeHandler.range = {type: 'list', values: [0, 1, 2, 3]};

			} else if( attributeHandler.nameattr == 'access_format' ) {
				attributeHandler.range = {type: 'list', values: ["'text/html'", "'text/xml'","'text/plain'"
				                                                 , "'application/fits'","'application/x-votable+xml'", "'application/pdf'"
				                                                 , "'image/png'", "'image/jpeg'", "'image/gif'", "'image/bmp'"]};
			}
		},
		/**
		 * Filter the displayed fields with the pattern typed by the user
		 */
		fireFilter : function(){
			$('#' + this.fieldTableId).html('');
			for( var i in this.attributesHandlers  ) {
				var ah = this.attributesHandlers[i];
				if( (this.filterPattern.test(ah.nameorg)  || 
						this.filterPattern.test(ah.nameattr) || 
						this.filterPattern.test(ah.ucd)      || 
						this.filterPattern.test(ah.comment)) ) {
					this.displayField(ah);
				}
			}
		},
		getAttributeHandler: function(ahname){
			return this.attributesHandlers[ahname];
		}
};

/**
 * Sub-class of FieldList_mVc, specialized to display UCDs instead of Fields
 * Same constructor as the superclass
 * Only the draw method is overloaded
 * @param parentDivId
 * @param formName
 * @param handlers
 * @returns {UcdFieldList_mVc}
 */
function UcdFieldList_mVc(parentDivId, formName, handlers){
	FieldList_mVc.call(this, parentDivId, formName, handlers);
};

/**
 * Method overloading
 */
UcdFieldList_mVc.prototype = Object.create(FieldList_mVc.prototype, {
	displayField: {
		value: function(ah){
			var that = this;
			var id = this.formName + "_" + ah.ucd;
			var stackId = "stack_" + id ;
			var title = ah.ucd 
			+ " - description: " +  ah.comment
			;
			var row ="<tr class=attlist id='" + ah.ucd + "'>" 
			+"<td class=attlist><span title='" + title + "'>"+ ah.ucd+"</span></td>"
			;
			if( this.stackHandler != null ) {
				row += "<td class='attlist attlistcmd'>"
					+"<input id=" + stackId + " title=\"Click to constrain fields with this UCD\"  class=\"stackconstbutton\" type=\"button\"></input>"
					+"</td>";
			}
			row += "</tr>";
			$('#' + this.fieldTableId).append(row);
			if( this.stackHandler != null ){
				$('#' + this.fieldTableId  + ' input[id="' + stackId + '"]').click(function() {
					that.stackHandler($(this).closest("tr").attr("id"));}
				);
			}
			$('#' + this.fieldTableId +  ' tr[id="' + ah.ucd + '"] span').tooltip( {
				track: true,
				delay: 0,
				showURL: false,
				opacity: 1,
				fixPNG: true,
				showBody: " - ",
				top: -15,
				left: 5
			});
		}
	},
	/**
	 * Draw all fields in the container
	 * Fields are described by the attribute handler array ahs
	 * Warning ahs is  not a map but an array 
	 */
	displayFields : {
		value : function(){
			var that = this;						
			this.attributesHandlers =new Array();
			MetadataSource.getTableAtt(
					this.dataTreePath
					, function() {
						var ahm = MetadataSource.ahMap(that.dataTreePath);
						var table  = "<table id=" + that.fieldTableId + " style='width: 100%; border-spacing: 0px; border-collapse:collapse'></table>";
						$('#' + that.fieldListId).html(table);
						for( var k=0 ; k<ahm.length ; k++) {
							var ah = ahm[k];
							that.attributesHandlers[ah.ucd] = ah;				
							that.displayField(ah);
						}
					});
		}
	} 
});

/**
 * Sub-class of FieldList_mVc, specialized to display UCDs instead of Fields
 * Same constructor as the superclass
 * Only the draw method is overloaded
 * @param parentDivId
 * @param formName
 * @param handlers
 * @returns {UcdFieldList_mVc}
 */
function CatalogueList_mVc(parentDivId, formName, handlers){
	FieldList_mVc.call(this, parentDivId, formName, handlers);
};

/**
 * Method overloading
 */
CatalogueList_mVc.prototype = Object.create(FieldList_mVc.prototype, {
	/**
	 * Draw the field container
	 */
	draw :  {
		value: function() {
			var that = this;
			this.attributesHandlers = new Array();
			this.parentDiv.html('<div class=fielddiv><div class="fieldlist" id="' + this.fieldListId
					+  '"></div><input id="' 
					+ this.fieldFilterId
					+  '" class="fieldfilter" type="text" value="Kw Filter" size=15 style="font-style: italic; color: LightGray"/></div>');
			$('#' + this.fieldFilterId).keyup(function() {
				that.filterPattern = new RegExp($(this).val(), 'i');
				that.fireFilter();
			});
			$('#' + this.fieldFilterId).one("click",function() {
				$(this).css('color','black');
				$(this).css('font-style','');
				$(this).attr('value','');
			});
		}	
	},
	/**
	 * Draw one field in the container
	 * Field described by the catalogue description
	 */
	displayField: {
		value: function(ah){
			var that = this;
			var id = this.formName + "_" + ah.CLASSNAME;
			var title = ah.ACDS_CATNAME 
			+ " - ACDS_CATACRO " +  ah.ACDS_CATACRO
			+ " - ACDS_CATCDSTB: " +  ah.ACDS_CATCDSTB
			+ " - ACDS_CATCONF: " +  ah.ACDS_CATCONF
			+ " - ACDS_CATINTNB: " +  ah.ACDS_CATINTNB
			+ " - ACDS_CATNAME: " +  ah.ACDS_CATNAME
			+ " - ACDS_CDSCAT: " +  ah.ACDS_CDSCAT
			+ " - VIZIER_KW: " +  ah.VIZIER_KW
			+ " - CLASSNAME: " +  ah.CLASSNAME

			;
			var row ="<tr class=attlist id='" + ah.CLASSNAME + "'>" 
			+"<td class=attlist style='width: 25%; overflow: hidden'><span title='" + title + "'>"+ ah.ACDS_CATACRO+"</span></td>"
			//+"<td class='attlist help'>" + ah.ACDS_CATINTNB +"</td>"
			+"<td class='attlist help'>" + ah.ACDS_CATNAME +"</td>"
			;
			if( this.stackHandler != null ) {
				row += "<td class='attlist attlistcmd' style='width: 16px;'>"
					+"<input id=stack_" + id + " title=\"Click to constrain this field\"  class=\"stackconstbutton\" type=\"button\"></input>"
					+"</td>";
			}
			row += "</tr>"; 
			$('#' + this.fieldTableId).css('table-layout', 'fixed');
			$('#' + this.fieldTableId).append(row);
			if( this.stackHandler != null ){
				$('#stack_' + id ).click(function() {that.stackHandler($(this).closest("tr").attr("id"));});
			}
			$('#' + this.fieldTableId +  ' tr[id="' + ah.CLASSNAME + '"] span').tooltip( {

				//$("tr#" + ah.CLASSNAME + " span").tooltip( {
				track: true,
				delay: 0,
				showURL: false,
				opacity: 1,
				fixPNG: true,
				showBody: " - ",
				// extraClass: "pretty fancy",
				top: -15,
				left: 5
			});
		}
	},
	/**
	 * Draw all fields in the container
	 * Fields are described by the attribute handler array ahs
	 * Warning ahs is  not a map but an array 
	 */
	displayFields : {
		value : function(ahs){
			this.attributesHandlers =new Array();
			for( var i=0 ; i<ahs.length ; i++){
				var ah = ahs[i];
				this.attributesHandlers[ah.CLASSNAME] = ah;
			}
			var table  = "<table id=" + this.fieldTableId + " style='width: 100%; border-spacing: 0px; border-collapse:collapse'></table>";
			$('#' + this.fieldListId).html(table);
			for( var i in this.attributesHandlers  ) {
				this.displayField(this.attributesHandlers[i]);
			}
		}
	},
	/**
	 * returns the AH named ahanme
	 */
	getAttributeHandlerByName: {
		value: function(ahname) {
			for( var ahn in this.attributesHandlers ) {
				var ah = this.attributesHandlers[ahn];
				if( ah.nameaatr == ahname ) {
					return ah;;	
				}
			}
			return nul;
		}
	},
	/**
	 * Filter the displayed fields with the pattern typed by the user
	 */
	fireFilter :  {
		value: function(){
			$('#' + this.fieldTableId).html('');
			for( var i in this.attributesHandlers  ) {
				var ah = this.attributesHandlers[i];
				if( (this.filterPattern.test(ah.ACDS_CATACRO)  || 
						this.filterPattern.test(ah.ACDS_CATCDSTB) || 
						this.filterPattern.test(ah.ACDS_CATCONF)      || 
						this.filterPattern.test(ah.ACDS_CATINTNB) ||
						this.filterPattern.test(ah.ACDS_CATNAME) ||
						this.filterPattern.test(ah.ACDS_CDSCAT) ||
						this.filterPattern.test(ah.VIZIER_KW) ||
						this.filterPattern.test(ah.CLASSNAME) 
				)) {
					this.displayField(ah);
				}
			}
		}
	}
});


function TapColList_mVc(parentDivId, formName, handlers, sessionID){
	FieldList_mVc.call(this, parentDivId, formName, handlers);
	this.tableSeclectId = parentDivId + "_tableSelect";
	this.joinedTableLoaded = false;
};

/**
 * Column selector for ADQL
 * Method overloading
 */
TapColList_mVc.prototype = Object.create(FieldList_mVc.prototype, {
	draw : {
		value: function() {
			var that = this;
			this.attributesHandlers = new Array();
			this.parentDiv.html('<div class=fielddiv><div class="fieldlist" id="' + this.fieldListId +  '" style="height: 155px"></div>'
					+ '  <div  style="position:absolute;width: 350px; top: 170px; left: 0px;height: 25px">'
					+ '       <input id="' + this.fieldFilterId +  '" class="fieldfilter" type="text" value="Kw Filter" size=15 style="font-style: italic; color: LightGray"/>'
					+ '        <select id="' + this.tableSeclectId +  '"  class="table_filter"  style="width: 150px">'
					+ '        </select>'
					+ '  </div>');
			$('#' + this.fieldFilterId).keyup(function() {
				that.filterPattern = new RegExp($(this).val(), 'i');
				that.fireFilter();
			});
			$('#' + this.fieldFilterId).one("click",function() {
				$(this).css('color','black');
				$(this).css('font-style','');
				$(this).attr('value','');
			});		
			this.setChangeTableHandler();
		}
	},
	setChangeTableHandler: {
		value: function() {
			var that = this;
			$('#' + this.tableSeclectId).change(function() {
				var to = this.value;
				var fs = to.split('.');
				var schema, table;
				$('#' + that.fieldFilterId).val(""); 
				if( fs.length == 2 ){
					schema = fs[0];
					table = fs[1];
				} else {
					/*
					 * If no schema in table name, we suppose the new tablme to belong the same schema
					 */
					schema = that.treePath.schema;
					table = fs[0];
				}
				that.changeTable(new DataTreePath({nodekey: that.dataTreePath.nodekey, schema: schema, table: table}));
			});
		}
	},
	setDataTreePath: {
		value: function(dataTreePath){
			this.dataTreePath = dataTreePath;
			$('#' +  this.fieldListId ).html('');
			$('#' +  this.tableSeclectId ).html('');
			this.addTableOption(this.dataTreePath );
			this.changeTable(this.dataTreePath);	
		}
	},
	addTableOption: {
		value: function(treePath){
			$('#' + this.tableSeclectId).append('<option>' + treePath.schema + '.' + treePath.table + '</option>');
			//$('#' + this.tableSeclectId).append('<option>' + treePath.tableorg + '</option>');
		}
	},
	changeTable : {
		value: function(dataTreePath) {	
			this.dataTreePath = dataTreePath;
			this.displayFields();
		}
	},
	getAttributeTitle: {
		value:  function(ah) {
			return ah.nameorg 
			+ " - node; " +  this.dataTreePath.nodekey
			+ " - schema; " +  this.dataTreePath.schema
			+ " - table; " +  this.dataTreePath.table
			+ " - description: " +  ah.comment
			+ " - UCD: " +  ah.ucd
			+ " - Unit: " +  ah.unit
			+ " - Type: " +  ah.type
			+ " - Range: " +  ((ah.range == null || ah.range.values == null)? 'Not Set':JSON.stringify( ah.range.values).replace(/'/g,"&#39;"))
			;
		}
	},
	displayFields : {
		value : function(){
			var that = this;
			this.attributesHandlers =new Array();
			MetadataSource.getTableAtt(
					this.dataTreePath
					, function() {
						var ahm = MetadataSource.ahMap(that.dataTreePath);
						var table  = "<table id=" + that.fieldTableId + " style='width: 100%; border-spacing: 0px; border-collapse:collapse'></table>";
						$('#' + that.fieldListId).html(table);
						that.attributesHandlers = new Array();
						for( var k=0 ; k<ahm.length ; k++) {
							var ah = ahm[k];
							that.attributesHandlers[ah.nameattr] = ah;				
							that.displayField(ah);
						}
						if( !that.joinedTableLoaded ) {
							var jt = MetadataSource.joinedTables(that.dataTreePath);
							for( var k=0 ; k<jt.length ; k++) {
								that.addTableOption(jt[k].target_datatreepath);
							}
							/*
							 * The same object is used with different columns sets: no cache for joined tables
							 */
							//that.joinedTableLoaded = true;
						}
						that.lookForAlphaKeyword();
						that.lookForDeltaKeyword();
					});	

		}
	} ,
	lookForAlphaKeyword: {
		value: function(ah) {}
	},
	lookForDeltaKeyword: {
		value: function(ah) {}
	},


});


/**
 * Field list with RA/DEC field selector
 * @param parentDivId
 * @param formName
 * @param handlers
 * @param getTableAttUrl $.getJSON("gettableatt", {jsessionid: sessionID, node: nodekey, table:newTable }
 * @tables {node: nodekey, table:newTable}
 */
function TapFieldList_mVc(parentDivId, formName, handlers, getTableAttUrl, sessionID){
	TapColList_mVc.call(this, parentDivId, formName, handlers, getTableAttUrl, sessionID);
	this.raFieldId = parentDivId + "_rafield";
	this.decFieldId = parentDivId + "_decfield";
	this.alphakw = null;
	this.deltakw = null;

	this.getTableAttUrl = getTableAttUrl;
	if( handlers.raHandler == null ){
		this.raHandler= function(ah){this.setAlphaKeyword(ah);};
	} else {
		this.raHandler= function(ah){this.setAlphaKeyword(ah);handlers.raHandler(ah);};
	}
	if( handlers.decHandler == null ){
		this.decHandler= function(ah){this.setDeltaKeyword(ah);};
	} else {
		this.decHandler= function(ah){this.setDeltaKeyword(ah);handlers.decHandler(ah);};
	}
};


/**
 * Method overloading
 */
TapFieldList_mVc.prototype = Object.create(TapColList_mVc.prototype, {
	draw : {
		value: function() {
			var that = this;
			this.attributesHandlers = new Array();
			this.parentDiv.html('<div class=fielddiv><div class="fieldlist" id="' + this.fieldListId +  '" style="height: 155px"></div>'
					+ '  <div  style="position:absolute;width: 350px; top: 170px; left: 0px;height: 45px">'
					+ '    <div style="position:absolute;width: 75px; top: 0px; left: 0px;height: 45px">'
					+ '       <input id="' + this.fieldFilterId +  '" class="fieldfilter" type="text" value="Kw Filter" size=15 style="font-style: italic; color: LightGray"/>'
					+ '        <br><select id="' + this.tableSeclectId +  '"  class="table_filter"  style="width: 70px">'
					+ '        </select>'
					+ '    </div>'
					+ '    <div style="position:absolute;width: 260px; top: 0px; left: 80px;height: 45px">'
					+ '      <span  class=help>ra&nbsp;&nbsp;</span>'
					+ '      <div id="' + this.raFieldId +  '"  class=radecfield style="top: 0px; left: 20px"/>'
					+ '      <span class=help style="position:absolute; top: 25px; left: 0px;">dec&nbsp;</span>'
					+ '      <div id="' + this.decFieldId +  '"  class=radecfield style="top: 20px; left: 20px"/>'
					+ '    </div>'
					+ '  </div>');
			$('#' + this.fieldFilterId).keyup(function() {
				that.filterPattern = new RegExp($(this).val(), 'i');
				that.fireFilter();
			});
			$('#' + this.fieldFilterId).one("click",function() {
				$(this).css('color','black');
				$(this).css('font-style','');
				$(this).attr('value','');
			});		
			this.setChangeTableHandler();
		}
	},
	setTreePath: {
		value: function(dataTreePath){
			this.dataTreePath = dataTreePath;
			$('#' +  this.fieldListId ).html('');
			$('#' +  this.tableSeclectId ).html('');
			$('#' + this.raFieldId).html('');
			this.alphakw = null;
			$('#' + this.decFieldId).html('');
			this.deltakw = null;
			this.addTableOption(this.dataTreePath );
			this.changeTable(this.dataTreePath);	
		}
	},
	changeTable : {
		value: function(dataTreePath) {	
			this.dataTreePath = dataTreePath;
			this.displayFields();
			/*
			this.lookForAlphaKeyword();
			this.lookForDeltaKeyword();
			 */
		}
	},
	lookForAlphaKeyword: {
		value: function(ah) {
			for( var ahn in this.attributesHandlers ) {
				var ah = this.attributesHandlers[ahn];
				if( ah.ucd == "pos.eq.ra;meta.main" ) {
					this.setAlphaKeyword(ahn);	
					return;
				}
			}
			for( var ahn in this.attributesHandlers ) {
				var ah = this.attributesHandlers[ahn];
				if( ah.ucd  == "pos.eq.ra" || ah.ucd.match( /POS_EQ_RA/i)) {
					this.setAlphaKeyword(ahn);	
					return;
				}
			}
			for( var ahn in this.attributesHandlers ) {
				var ah = this.attributesHandlers[ahn];
				if( ah.nameattr  == "s_ra" || ah.nameattr  == "pos_ra_csa"|| ah.nameattr  == "_sc_ra" 
					|| ah.nameattr.toUpperCase()  == "RA" || ah.nameattr.toUpperCase()  == "RAJ2000") {
					this.setAlphaKeyword(ahn);	
					return;
				}
			}
		}
	},
	setAlphaKeyword: {
		value: function(ahname) {
			$('#' + this.raFieldId).html('');
			this.alphakw  = new TapKWSimpleConstraint_mVc({divId: this.raFieldId
				, constListId: this.raFieldId
				, isFirst: true
				, attributeHandler: this.attributesHandlers[ahname]
			, editorModel: null
			, defValue: ''
				, treePath: jQuery.extend({}, this.dataTreePath)});
			this.alphakw.fireInit();
		}
	}
	,
	getRaKeyword: {
		value: function(ahname) {
			return $('#' + this.raFieldId+ " span" ).text();
		}
	},

	lookForDeltaKeyword: {
		value: function(ah) {
			for( var ahn in this.attributesHandlers ) {
				var ah = this.attributesHandlers[ahn];
				if( ah.ucd == "pos.eq.dec;meta.main" ) {
					this.setDeltaKeyword(ahn);	
					return;
				}
			}
			for( var ahn in this.attributesHandlers ) {
				var ah = this.attributesHandlers[ahn];
				if( ah.ucd  == "pos.eq.dec" || ah.ucd.match( /POS_EQ_DEC/i) ) {
					this.setDeltaKeyword(ahn);	
					return;
				}
			}
			for( var ahn in this.attributesHandlers ) {
				var ah = this.attributesHandlers[ahn];
				if( ah.nameattr  == "s_dec"|| ah.nameattr  == "pos_dec_csa" || ah.nameattr  == "_sc_dec" 
					|| ah.nameattr.toUpperCase()  == "DEC" || ah.nameattr.toUpperCase()  == "DECJ2000" ) {
					this.setDeltaKeyword(ahn);	
					return;
				}
			}
		}
	},
	setDeltaKeyword: {
		value: function(ahname) {
			$('#' + this.decFieldId).html('');
			this.deltakw  = new TapKWSimpleConstraint_mVc({divId: this.decFieldId
				, constListId: this.decFieldId
				, isFirst: true
				, attributeHandler: this.attributesHandlers[ahname]
			, editorModel: null
			, defValue: ''
				, treePath: jQuery.extend({}, this.dataTreePath)});
			this.deltakw.fireInit();
		}
	},
	getDeltaKeyword: {
		value: function(ahname) {
			return $('#' + this.decFieldId + " span" ).text();
		}
	}
});


console.log('=============== >  FieldList_v.js ');

/**
 * Sorter_mVc: Small widget commanding column sorting (ascedent or descendant). The sorter layout includes the 2 arrows and the the filed label.
 * It corresponds to the whole content of the container. 
 * The Sorter is supposed to belongs to a TR container
 * @param container       : JQuery ref of container. It must exist with the DOM  before.
 * @param parentContainer : JQUERY ref of the container containing all coupled sorters (typically all row heads)
 * @param fieldAh         : Attribute handler of the field: namorg is displayed and nameattr is sent to the sort handler
 * @param  handler        : handler called   when  the state of the arrows change. 
 *                          This handler must have be prototyped as function(field, sort)
 *                          where field is the name of the filed given to the constructor
 *                          and sort is the command. It is equals to 
 *                          	- "asc" : for ascendant sort
 *                              - "desc": for descendant sort
 *                              - null  : for no sort
 */

/**
 * class prototype
 */
function Sorter_mVc(container, parentContainer, fieldAh, handler){

	/*
	 * Some reference and IDs on useful  DOM elements
	 */
	this.container = container;
	this.parentContainer = parentContainer;
	this.fieldAh = fieldAh;
	this.handler = (handler == null)? function(fieldName, sortCommand){alert("Sort command: " + fieldName + " " +  sortCommand);}: handler;
}
/**
 * Methods prototypes
 */
Sorter_mVc.prototype = {
		/**
		 * Draw the field container
		 */
		draw : function() {
			var that = this;
			this.container.html("<div style='display: inline;'>"
					+ "<div class=sorter><a class=sort_asc_disabled href='#'></a><a class=sort_desc_disabled href='#'></a></div>"
					+ "<span class=sorter>" + this.fieldAh.nameorg+ "</span>"
					+ "</div>");
			this.container.find("a").click(function(){
				var clickedAnchor = $(this);
				that.enableDisable(clickedAnchor);
				var nodeClass = clickedAnchor.attr("class");
				if( nodeClass == "sort_asc" ) {
					that.handler(that.fieldAh.nameattr, "asc");
				} else if( nodeClass == "sort_desc" ) {
					that.handler(that.fieldAh.nameattr, "desc");
				} else {
					that.handler(that.fieldAh.nameattr, null);
				}
			});
		},
		enableDisable: function(clickedAnchor) {
			var initClass =  clickedAnchor.attr("class");
			
			this.parentContainer.find('a').each(function() {
				var node = $(this);
				var nodeClass = node.attr("class");
				if( nodeClass == "sort_asc") {
					node.attr("class","sort_asc_disabled" );
				} else if( nodeClass == "sort_desc") {
					node.attr("class","sort_desc_disabled" );
				} 
			});
			clickedAnchor.attr("class",
					(initClass == "sort_asc_disabled")? "sort_asc"
							:(initClass == "sort_asc")? "sort_asc_disabled"
									: (initClass == "sort_desc_disabled")? "sort_desc"
											: "sort_desc_disabled");
					
		},
		activeArrow: function(asc) {
			this.container.find("a").each(function() {
				var node = $(this);
				var nodeClass = node.attr("class");
				if( nodeClass == "sort_asc_disabled" && asc) {
					node.attr("class","sort_asc" );
				} else if( nodeClass == "sort_desc_disabled" && !asc ) {
					node.attr("class","sort_desc" );
				} 
				
				
			});
		}
};



console.log('=============== >  Sorter_v.js ');

/**
 * @returns {QueryTextEditor_Mvc}
 */
function DataLink_Mvc() {
	this.formEntries =  new Array();
	this.listener = null;
};

DataLink_Mvc.prototype = {
		addListener : function(list){
			this.listener = list;
		},
		storeWebService: function(webservice, url) {
			this.formEntries[webservice] = {url: url, loaded: false};
		},
		getUrl: function(webservice) {
			return ( this.formEntries[webservice] == null ) ? null: this.formEntries[webservice].url;
		},
		webServiceLoaded: function(webservice) {
			if( this.formEntries[webservice] != null ) {
				this.formEntries[webservice].loaded = true;
			}
		},
		isWebServiceLoaded: function(webservice) {
			return ( this.formEntries[webservice] == null ) ? false: this.formEntries[webservice].loaded;
		}
};
console.log('=============== >  DataLink_m.js ');


/**
 * @param params { parentDivId: 'query_div',baseurl}
 * @returns
 */
function DataLink_mVc(params) {
	this.parentDiv = $("#" +params.parentDivId );
	this.baseurl = params.baseurl;
	this.forwardurl = params.forwardurl;
	this.textareaid = params.parentDivId + "_text";

	this.listener = null;
}
DataLink_mVc.prototype = {

		addListener : function(list){
			this.listener = list;
		},
		draw : function(){	
			var that = this;
			var url = (this.forwardurl != null )? (this.forwardurl+ "?target=" + encodeURIComponent( this.baseurl))
					: this.baseurl;
			Processing.show("Fetching Datalink Description at " + url);
			$.ajax({
				type: "GET",
				url: url,
				dataType: "xml",      
				error: function (xhr, ajaxOptions, thrownError) {
					Processing.hide();
					Modalinfo.error(url + "\n" + xhr.responseText + "\n" + thrownError);
				},
				success: function(xml) {
					Processing.hide();
					if( $(xml).find('VOTABLE').length != 0 ){
						that.processVOTABLEResponse(xml);
					} else { 
						that.processXMLResponse(xml);
					}
				}});
		},
		processVOTABLEResponse: function(xml){
			var html = "";
			var hasLink = false;
			$(xml).find('TR').each(function(){
				var i=0;
				var uri ="";
				var url ="";
				var semantic ="";
				var productType ="";
				var contentType ="";
				var size ="";
				
				var type = $(this).find('TD').each(function(){
					hasLink = true;
					switch(i){
					case 0: uri = $(this).text();break;
					case 1: url = $(this).text();break;
					case 2: productType = $(this).text();break;
					case 3: semantic = $(this).text();break;
					case 4: contentType = $(this).text();break;
					case 5: size = $(this).text();break;
					}
					i++;
				});
				html += "<fieldset>";
				html += "  <legend><span>Link <i>" + productType + "</i> <span></legend>";
				html += "    <a class=dldownload href='#' onclick='PageLocation.changeLocation(&quot;" + url + "&quot);' title='Download link target'></a>";
				html += "    <span class=help>" +  semantic + "</span><br>";
				html += "    <span class=help><b>Product URI:</b> " +  uri + "</span><br>";
				html += "    <span class=help><b>Content:</b> " +  contentType + " " +  ((size == 0)? "": size +"b") + "</span>";
				html += "</fieldset>";
			});
			if( hasLink){
			Modalcommand.commandPanel("Link Browser", html);
			$("div.datalinkform").toggle();
			Modalcommand.setDivToggling(function() {that.buildWebserviceForm($(this).attr("id"));});
			} else {
				Modalinfo.info("No link available", "Datalink Info");
			}
		},
		processXMLResponse: function(xml){
			var that = this;
			var html = "";
			$(xml).find('endpoint').each(function(){
				var type = $(this).attr('type');
				var datatype = $(this).attr('datatype');
				html += "<fieldset>";
				html += "  <legend><span>Link <i>" + $(this).attr('name') + "</i> (" + datatype + ")<span></legend>";
				if( type == "download" ) {
					html += that.buildDownloadForm($(this), datatype);
				} else if( type == "webservice" ) {							
					html += that.buildWebserviceEntry($(this));
				} else {
					Modalinfo.error("Unknown link type " + type);
				}
				html += "</fieldset>";
			});
			Modalcommand.commandPanel("Link Browser", html);
			$("div.datalinkform").toggle();
			Modalcommand.setDivToggling(function() {that.buildWebserviceForm($(this).attr("id"));});
		},
		buildDownloadForm : function(xml, datatype) {
			var url = (this.forwardurl != null )? (this.forwardurl+ encodeURIComponent( xml.find('url').text()))
					: xml.find('url').text();
			url =  xml.find('url').text();
			var clk = (datatype == "preview")? "Modalinfo.openIframePanel(&quot;" +  url + "&quot);" 
					: "PageLocation.changeLocation(&quot;" +  url + "&quot);";
			var html = "<div>";
			html += "    <a class=dldownload href='#' onclick='" + clk + "' title='Download link target'></a>";
			html += "    <span class=help>" +  xml.find('description').text() + "</span>";
			html += "</div>";
			return html;	
		},
		buildWebserviceEntry : function(xml) {
			var xmlurl =  xml.find('url').text();
			var url = (this.forwardurl != null )? (this.forwardurl+ encodeURIComponent( xmlurl))
					: xmlurl;
			var webservice = xml.attr("name");
			this.fireStoreWebService(webservice, xmlurl);
			var description = xml.find('description').text() ;
			return "<div id=" + webservice + " class=datalinkform >"
			+ "    <ul>"
			+ "      <li>Decription " + description + "</li>"
			+ "      <li>type " +  xml.attr('type')+ "</li>"
			+ "      <li>URL <a href='#' onclick='Modalinfo.openIframePanel(&quot;" +  url + "&quot, &quot;" +  description + "&quot);'>click</a>"+ "</li>"
			+ "    </ul>"
			+ "</div>";
		},
		buildWebserviceForm : function(webservice) {
			if( webservice != null && !this.fireIsWebServiceLoaded(webservice) ) {
				var url = (this.forwardurl != null )? (this.forwardurl+ "?target=" +  encodeURIComponent(this.fireGetUrl(webservice)))
						: this.fireGetUrl(webservice);
				//var url = "forwardxmlresource?target=" +  encodeURIComponent(this.fireGetUrl(webservice));
				var that = this;
				var sliders = new Array();
				var fields = new Array();
				Processing.show("Build form for webservice " + webservice);
				$.ajax({
					type: "GET",
					url: url,
					dataType: "xml",
					error: function (xhr, ajaxOptions, thrownError) {
						Processing.hide();
						Modalinfo.error(url + "\n" + xhr.responseText + "\n" + thrownError);
					},
					success: function(xml) {
						Processing.hide();
						$('#' + webservice).html(''); 
						var baseurl = $(xml).find('baseurl').text();
						var html = "";
						html += "    <a class=dldownload href='#' id='" + webservice + "_submit' title='Submit the query'></a>";
						html += "    <span class=help>" +   $(xml).find('description').first().text() + "</span><br>";
						$('#' + webservice).append(html); 
						$(xml).find('parameter').each(function(){
							var name = $(this).attr('name');
							var id  = webservice + "_" + $(this).attr('name');
							html  = "    <span><b>" +  name + "</b></span>";
							html += "    <span class=help>" +  $(this).find('description').text() + "</span><br>";
							var range = $(this).find('range');
							var type = range.attr('type');
							if( type == "enum" ) {
								html += "&nbsp;&nbsp;<span>Value</span>&nbsp;";
								html += "<select id=" + id + '_input' + ">";
								$(range).find('value').each(function() {	
									var value = $(this);
									var selected = ( value.attr("type") == "default")? "selected": "";
									html += "<option " + selected + ">" + $(this).text() + "</option>";
								});
								html += "</select><br>";
								fields.push(name);
							} else if( type == "range" ) {
								var min=0, max=0, def=0;
								$(range).find('value').each(function() {	
									var value = $(this);
									var type = value.attr("type");
									if( type == "default") {
										def = $(this).text();
									} else if( type == "min") {
										min = $(this).text();
									} else if( type == "max") {
										max = $(this).text();
									}
								});
								var fmin = parseFloat(min);
								var fmax = parseFloat(max);
								var fdef = parseFloat(def);
								if( fmin >= fmax ||fdef < fmin ||fdef > fmax ) {
									Modalinfo.error("Unconsistant range (min=" + min + " max="+ max + " def=" + def + ") no input for this parameter");
								} else {
									html += '&nbsp;&nbsp;<label for="amount">Value</label>'
										+ ' <input type="text" id="' + id + '_input" style="width: 50px; border: 0;font-weight: bold;" value="' + def + '"/>'
										+ '<div style="display: inline-block;width: 300px" id="' + id + '"></div><br>'    ;
									sliders[id] = {id: id, min: fmin, max: fmax, def: fdef};
									fields.push(name );
								}
							} else {
								Modalinfo.error("Unconsistant range type " + type + " no input for this parameter");
							}
							$('#' + webservice).append(html); 
							for( var p in sliders) {
								var slider = sliders[p];
								$( "#" + slider.id ).slider({
									range: "min",
									value: slider.def,
									min: slider.min,
									max: slider.max,
									step: ((slider.max - slider.min)/20),
									slide: function( event, ui ) {
										$( "#" +  $(this).attr("id")+ '_input').val(ui.value );
									}
								});

							}
							that.fireWebServiceLoaded(webservice);
						});
						$( "#" + webservice + "_submit" ).click(function(){
							var query = baseurl;
							for( var f=0 ; f<fields.length ; f++ ) {
								query += "&" + fields[f] + "=" + $('#' + webservice + "_" + fields[f] + '_input').val();
							}
							Modalinfo.openIframePanel(query, webservice);
						});
					}});
			}
		},
		fireGetQuery: function() {
			return $("#" + this.textareaid ).val();
		},		
		/*
		 *	Params: {type, constraints}
		 *	where supported typed are "column" "orderby" "ucd" "position" "relation" "limit"
		 *  Label is used to identify the form  constraints are coming from
		 */
		fireStoreWebService : function(webservice, url) {
			this.listener.controlStoreWebService(webservice, url);
		},
		fireGetUrl: function(webservice) {
			return this.listener.controlGetUrl(webservice);
		},
		fireWebServiceLoaded: function(webservice) {
			this.listener.controlWebServiceLoaded(webservice);
		},
		fireIsWebServiceLoaded: function(webservice) {
			return this.listener.controlIsWebServiceLoaded(webservice);
		}

};

console.log('=============== >  DataLink_v.js ');

DataLink_mvC = function(view, model){
	/**
	 * listen to the view
	 */
	var vlist = {
			controlStoreWebService : function(webservice, url){
				return model.storeWebService(webservice, url);
			},
			controlGetUrl : function(webservice){
				return model.getUrl(webservice);
			},
			controlWebServiceLoaded : function(webservice){
				model.webServiceLoaded(webservice);
			},
			controlIsWebServiceLoaded : function(webservice){
				return model.isWebServiceLoaded(webservice);
			}
	};
	view.addListener(vlist);

	var mlist = {
	};
	model.addListener(mlist);
};

console.log('=============== >  DataLink_c.js ');

/**
 * ConstQEditor_Mvc: Model of the constraint query editor
 * 
 * @param chemin : URL returning metadata (format given bellow)
 * @returns {ConstQEditor_Mvc}
 */
function ConstQEditor_Mvc(){
	this.listeners = new Array();
	this.const_key = 1;
	this.attributeHandlers = new Array();
	this.attributeHandlersSearch = new Array();
	this.editors = new Array();
	this.constEditorRootId = 'kwconsteditor_';
}

ConstQEditor_Mvc.prototype = {
		addListener : function(list){
			this.listeners.push(list);
		},
		loadFields : function(dataTreePath /* instance of DataTreePath */) {
			var that = this;
			this.attributesHandlers = {};
			if( dataTreePath ) {
				MetadataSource.getTableAtt(
						dataTreePath
						, function() {
							var ahm = MetadataSource.ahMap(dataTreePath);
							that.attributeHandlersSearch = new Array();
							for( var k=0 ; k<ahm.length ; k++) {
								var ah = ahm[k];
								that.attributeHandlersSearch.push(ah);		
								that.attributeHandlers[ah.nameattr] = ah;				
							}
						});
			}
		},
		addPresetValues : function(){
			// Nothing to do here but for TAP
		},		
		clearAllConst : function(){
			this.const_key = 1;
			this.editors = new Array();
			this.notifyTypoMsg(false, '');
			this.updateQuery();
		},
		clearConst : function(filter){
			this.notifyTypoMsg(false, '');
			for(  var e in this.editors) {
				if( e.match(filter) ) {
					this.editors[e].remove();
					this.const_key --;
				}
			}
			this.updateQuery();
		},

		edit : function(){
			return this.editors;
		},
		processAttributeEvent : function(ahname, constListId){
			this.processAttributeHandlerEvent(this.attributeHandlers[ahname], constListId);
		},
		processAttributeHandlerEvent : function(ah, constListId){
			var first = true;
			for( k in this.editors ) {
				first = false;
				break;
			}
			var divKey = this.constEditorRootId + ah.nameattr + this.const_key;
			Out.debug("mv constraint " + ah.nameattr + " to #" + constListId);
			var v = new KWConstraint_mVc({divId: divKey
				, constListId: constListId
				, isFirst: first
				, attributeHandler: ah
				, editorModel: this
				, defValue: ''});
			this.editors[divKey] = v;
			v.fireInit();
			this.const_key++;
		},
		processRemoveFirstAndOr : function(key) {
			delete this.editors[key];
			for( var k in this.editors ) {
				this.editors[k].fireRemoveAndOr();
				break;
			}
		},
		notifyNextListener : function(attr) {
			var that = this;
			$.each(this.listeners, function(i){
				that.listeners[i].nextListener(attr);
			});
		},
		updateQuery : function() {
			var that = this;
			var retour= "    ";
			for( var e in this.editors) {
				var q =  this.editors[e].fireGetADQL();
				if( retour.length > 96 ) retour += "\n    ";
				if( q != null ) {
					retour += " " + $.trim(this.editors[e].fireGetADQL());
				}
			}
			$.each(this.listeners, function(i){
				that.listeners[i].controlUpdateQuery(retour);
			});
		},
		getNumberOfEditor: function() {
			var retour = 0;
			for( var e in this.editors) {
				retour ++;
			}
			return retour;
		},
		notifyRunQuery : function() {
			var that = this;
			$.each(this.listeners, function(i){
				that.listeners[i].controlRunQuery();
			});
		},
		notifyTypoMsg : function(fault, msg){
			var that = this;
			$.each(this.listeners, function(i){
				that.listeners[i].controlTypoMsg(fault, msg) ;
			});
		},
		notifyFieldsStored: function(){
			var that = this;
			$.each(this.listeners, function(i){
				that.listeners[i].controlFieldsStored(that.attributeHandlersSearch) ;
			});
		},
		getAttributeHandlers: function() {
			return this.attributeHandlers;
		},
		// used by regions
		getDefaultValue: function() {
			return null;
		}

};

/**
 * Sub-class of ConstQEditor_Mvc, specialized to manage UCDs instead of Fields
 * Same constructor as the superclass
 * Only the UcdQEditor_Mvc method is overloaded
 * @param chemin
 */
function UcdQEditor_Mvc(){
	ConstQEditor_Mvc.call(this);
};
/**
 * Method overloading
 */
UcdQEditor_Mvc.prototype = Object.create(ConstQEditor_Mvc.prototype, {
	processAttributeEvent: {
		value : function(ahname, constListId){
			var ah = this.attributeHandlers[ahname];
			if( ah == null ){
				Out.info("No AH referenced by " + ahname);
			} else {
				var first = true;
				for( k in this.editors ) {
					first = false;
					break;
				}
				Out.debug("mv UCD constraint " + ahname + " to #" + constListId );
				var divKey = this.constEditorRootId + ahname + this.const_key;
				var v = new UCDConstraint_mVc({divId: divKey
					, constListId: constListId
					, isFirst: first
					, attributeHandler: ah
					, editorModel: this
					, defValue: ''});

				this.editors[divKey] = v;
				v.fireInit();
				this.const_key++;
			}
		}
	},
	loadFields: {
		value : function(dataTreePath /* instance of DataTreePath */) {
			var that = this;
			this.attributesHandlers = {};
			if( dataTreePath ) {
				MetadataSource.getTableAtt(
						dataTreePath
						, function() {
							var ahm = MetadataSource.ahMap(dataTreePath);
							that.attributeHandlersSearch = new Array();
							for( var k=0 ; k<ahm.length ; k++) {
								var ah = ahm[k];
								that.attributeHandlersSearch.push(ah);		
								that.attributeHandlers[ah.ucd] = ah;				
							}
						});
			}
		}
	},

	updateQuery : {
		value : function() {
			var that = this;
			var retour= "";
			var and = "";
			for( var e in this.editors) {
				var q = this.editors[e].fireGetADQL();
				if( q != null ) {
					retour += and + q;
					if( and == "" ) and = "\n        AND " ;
				}
			}
			$.each(this.listeners, function(i){
				that.listeners[i].controlUpdateQuery(retour);
			});
		}
	}
});

/**
 * Sub-class of ConstQEditor_Mvc, specialized to manage UCDs instead of Fields
 * Same constructor as the superclass
 * Only the UcdQEditor_Mvc method is overloaded
 * @param chemin
 */
function UcdPatternEditor_Mvc(chemin, relationName){
	UcdQEditor_Mvc.call(this, chemin);
	this.relationName = relationName;
};
/**
 * Method overloading
 */
UcdPatternEditor_Mvc.prototype = Object.create(UcdQEditor_Mvc.prototype, {
	updateQuery : {
		value : function() {
			var that = this;
			var retour= "";
			var and = "";
			for( var e in this.editors) {
				var q = this.editors[e].fireGetADQL();
				if( q != null ) {
					retour += and + q;
					if( and == "" ) and = "\n        AND " ;
				}
			}
			if( retour != "" ) {
				retour = "  matchPattern {" + this.relationName + ",\n"
				+ "    AssUCD{" + retour + "}\n"
				+ "  }";
			}
			$.each(this.listeners, function(i){
				that.listeners[i].controlUpdateQuery(retour);
			});
		} 
	}
});

/**
 * Sub-class of ConstQEditor_Mvc, specialized to manage Position search instead of Fields
 * Same constructor as the superclass
 * Only the UcdQEditor_Mvc method is overloaded
 * @param chemin
 */
function PosQEditor_Mvc(){
	ConstQEditor_Mvc.call(this, null);
};

/**
 * Method overloading
 */
PosQEditor_Mvc.prototype = Object.create(ConstQEditor_Mvc.prototype, {
	processAttributeEvent: {
		value : function(coneParams, constListId){
			if(coneParams == null){
				return;
			}
			/*
			 * create a pseudo ah for each searched position
			 */
			var ah = {
					nameorg: coneParams.position,
					nameattr: (coneParams.type == "region")? "region" : coneParams.position.replace(/[^0-9a-zA-Z_]+/g, '_'),
							ucd: '',
							unit: '',
							comment: 'cone search',
							radius:  coneParams.radius,
							frame:  coneParams.frame,
							value: coneParams.position
			};
			var divKey = this.constEditorRootId + ah.nameattr ;
			/*
			 * Only one constraint for a given position
			 */
			if( this.editors[divKey] == null ) {
				var first = true;
				for( k in this.editors ) {
					first = false;
					break;
				}

				var v = new PosConstraint_mVc({divId: divKey
					, constListId: constListId
					, isFirst: first
					, attributeHandler: ah
					, editorModel: this
					, defValue: ''});
				this.editors[divKey] = v;
				v.fireInit();
			} else {
				Modalinfo.error("Position " + ah.nameorg + " already searched");
			}
		}
	},
	storeFields : {
		value :function(data) {
			/*
			 * Stockage des AHs dans le modele
			 */
			this.attributeHandlers = new Array();
			this.attributeHandlersSearch = new Array();
			for(var i=0 ; i<data.attributes.length ; i++ ) {
				this.attributeHandlers[data.attributes[i].ucd] = data.attributes[i];
				this.attributeHandlersSearch[this.attributeHandlersSearch.length]= data.attributes[i];
			}
			this.notifyFieldsStored();
		}
	},		
	updateQuery : {
		value : function() {
			var that = this;
			var retour= "";
			for( var e in this.editors) {
				var q = this.editors[e].fireGetADQL();
				if( q != null ) {
					if( retour != "" ) retour += ",\n" ;
					retour += "    " + this.editors[e].fireGetADQL();
				}
			}
			$.each(this.listeners, function(i){
				that.listeners[i].controlUpdateQuery(retour);
			});
		}
	},
	getDefaultValue : {
		value :  function() {				
			for( var e in this.editors) {
			//	if( e.match(".region.*") ){
					return {type: "saadaql", value: this.editors[e].fireGetADQL()};
			//	}
			}

			return null;
		}
	}

});

/**
 * Sub-class of ConstQEditor_Mvc, specialized catalogue counterpart
 * Same constructor as the superclass
 * @param chemin
 */
function CatalogueQEditor_Mvc(params /*{parentDivId, formName, getMetaUrl, queryView, relationName, distanceQualifer, help}*/){
	ConstQEditor_Mvc.call(this, params);
	this.relationName = params.relationName;
	this.qualifier = params.distanceQualifer;
	this.getMetaUrl = params.getMetaUrl;
};

CatalogueQEditor_Mvc.prototype = Object.create(ConstQEditor_Mvc.prototype, {

	loadFields : {
		value: function(){
			var that = this;
			if( this.getMetaUrl != null ) {
				$.getJSON(this.getMetaUrl,function(data) {
					that.storeFields(data);
				});
			} else {
				this.storeFields(
						{attributes: [
						              {ACDS_CATACRO: "ACDS_CATACRO", ACDS_CATCDSTB: "ACDS_CATCDSTB", ACDS_CATCONF: "ACDS_CATCONF", ACDS_CATINTNB: "ACDS_CATINTNB", ACDS_CATINTNB: "ACDS_CATINTNB", ACDS_CATNAME: "ACDS_CATNAME", ACDS_CDSCAT: "ACDS_CDSCAT", VIZIER_KW: "VIZIER_KW", CLASSNAME: "CLASSNAME"}
						              ,{ACDS_CATACRO: "ACDS_CATACRO", ACDS_CATCDSTB: "ACDS_CATCDSTB", ACDS_CATCONF: "ACDS_CATCONF", ACDS_CATINTNB: "ACDS_CATINTNB", ACDS_CATINTNB: "ACDS_CATINTNB", ACDS_CATNAME: "ACDS_CATNAME", ACDS_CDSCAT: "ACDS_CDSCAT", VIZIER_KW: "VIZIER_KW", CLASSNAME: "CLASSNAME2"}
						              ]
						});
			}
		}
	},
	processAttributeEvent: {
		value : function(ahname, constListId){
			var ah = this.attributeHandlers[ahname];
			if( ah == null ) {
				Modalinfo.error("Internal error: No constraint referenced by the key '" + ahname + "' (see console)");	
				var ks = new Array();
				$.each(this.attributeHandlers, function(key, value) {
					ks.push(key);	

				});
				return;
			}
			var first = true;
			for( k in this.editors ) {
				first = false;
				break;
			}
			Out.debug("mv catalogue constraint " + ahname + " to #" + constListId );
			var divKey = this.constEditorRootId + ahname ;

			if( this.editors[divKey] != null ) {
				Modalinfo.error("Constraint on " + ahname + " already active: cannot be duplicated");
				return;
			}
			var v = new CatalogueConstraint_mVc({divId: divKey
				, constListId: constListId
				, isFirst: first
				, attributeHandler: ah
				, editorModel: this
				, defValue: ''
					, qualifier: this.qualifier});

			this.editors[divKey] = v;
			v.fireInit();
			this.const_key++;
		}
	},
	storeFields : {
		value :  function(data) {
			this.attributeHandlers = new Array();
			this.attributeHandlersSearch = new Array();
			for(var i=0 ; i<data.attributes.length ; i++ ) {
				this.attributeHandlers[data.attributes[i].CLASSNAME] = data.attributes[i];
				this.attributeHandlersSearch[this.attributeHandlersSearch.length]= data.attributes[i];
			}
			this.notifyFieldsStored();
		}
	},
	updateQuery : {
		value : function() {
			var that = this;
			var sq = "";
			for( var e in this.editors) {
				var q = this.editors[e].fireGetADQL();
				if( q != null && q != "" ) {
					if( sq != "" ) sq += "\n";
					sq += "    matchPattern { " + this.relationName + "," + q+ "}";
				}
			}
			$.each(this.listeners, function(i){
				that.listeners[i].controlUpdateQuery(sq);
			});
		} 
	}
});

/**
 * Sub-class of ConstQEditor_Mvc, specialized in catalogue counterpart with proba of identification
 * Same constructor as the superclass
 * @param chemin
 */
function CrossidQEditor_Mvc(params){
	CatalogueQEditor_Mvc.call(this, params);
};

CrossidQEditor_Mvc.prototype = Object.create(CatalogueQEditor_Mvc.prototype, {

	processAttributeEvent: {
		value : function(ahname, constListId){
			var ah = this.attributeHandlers[ahname];
			if( ah == null ) {
				Modalinfo.error("Internal error: No constraint referenced by the key '" + ahname + "' (see console)");	
				var ks = new Array();
				$.each(this.attributeHandlers, function(key, value) {
					ks.push(key);
				});
				return;
			}
			var first = true;
			for( k in this.editors ) {
				first = false;
				break;
			}
			Out.debug("mv catalogue constraint " + ahname + " to #" + constListId );
			var divKey = this.constEditorRootId + ahname ;

			if( this.editors[divKey] != null ) {
				Modalinfo.error("Constraint on " + ahname + " already active: canot be duplicated");
				return;
			}
			var v = new CrossidConstraint_mVc({divId: divKey
				, constListId: constListId
				, isFirst: first
				, attributeHandler: ah
				, editorModel: this
				, defValue: ''
					, qualifier: this.qualifier});

			this.editors[divKey] = v;
			v.fireInit();
			this.const_key++;
		}
	}
});

/**
 * Sub-class of ConstQEditor_Mvc, specialized in catalogue counterpart with proba of identification
 * Same constructor as the superclass
 * @param chemin
 */
function tapColSelector_Mvc(){
	ConstQEditor_Mvc.call(this);
	this.joinKeys = null;
	this.dataTreePath = null; // instance of DataTreePath
	this.joinedTableLoaded = false;
	this.orderBy = "";
};

tapColSelector_Mvc.prototype = Object.create(ConstQEditor_Mvc.prototype, {
	loadFields : {
		value: function(treePath, handler) {
			var that = this;
			if(treePath) {
				this.dataTreePath = jQuery.extend({}, treePath);			
				MetadataSource.getTableAtt(
						this.dataTreePath
						, function() {
							that.notifyAddTableOption(that.dataTreePath);	
							var ahm = MetadataSource.ahMap(that.dataTreePath);
							that.attributeHandlers = new Array();
							that.attributeHandlersSearch = new Array();
							for(var k=0 ; k<ahm.length ; k++ ) {
								var ah = ahm[k];
								that.attributeHandlers[ah.nameattr] = ah;
								that.attributeHandlersSearch[that.attributeHandlersSearch.length]=ah;
							}
							if( !that.joinedTableLoaded ) {
								that.joinKeys = new Array();
								var jt = MetadataSource.joinedTables(that.dataTreePath);
								for( var k=0 ; k<jt.length ; k++) {
									that.joinKeys.push(jt[k]);
								}
								that.joinedTableLoaded = true;
							}
							if( handler ) handler();
						});
			} else {
				this.attributeHandlers = new Array();
				this.attributeHandlersSearch = new Array();
				this.joinKeys = new Array();
				this.dataTreePath = null;
			}
		}
	},
	notifyAddTableOption: {
		value: function(treePath){
			var that = this;
			$.each(this.listeners, function(i){
				that.listeners[i].controlAddTableOption(treePath);
			});

		}
	},
	processAttributeHandlerEvent : {
		value: function(ah, constListId){
			var that = this;
			var first = true;
			var currentTreePath = null;
			for( k in this.editors ) {
				first = false;
				break;
			}
			$.each(this.listeners, function(i){
				currentTreePath  = that.listeners[i].controlCurrentTreePath();
			});
			var divKey = this.constEditorRootId + ah.nameattr + this.const_key;
			Out.debug("mv constraint " + ah.nameattr + " to #" + this.constListId);
			var v = new TapKWSimpleConstraint_mVc({divId: divKey
				, constListId: constListId
				, isFirst: first
				, attributeHandler: ah
				, editorModel: this
				, defValue: ''
					, treePath: jQuery.extend({}, currentTreePath)});
			this.editors[divKey] = v;
			v.fireInit();
			this.const_key++;
		}
	},
	processOrderBy: {
		value: function(nameattr) {
			this.orderBy = ( nameattr != 'OrderBy' )? nameattr: "";		
			this.updateQuery();
		}
	},
	updateQuery : {
		value: function() {
			var that = this;
			// queried table path
			var st = this.dataTreePath.schema + "." + this.dataTreePath.table;
			var joins = new Array();
			var q = new Array();
			/*
			 * Merge all constraints
			 */
			for( var e in this.editors) {
				var ed = this.editors[e];
				// Path of the table targeted by he current constraint
				var tt = ed.treePath.schema + "." + ed.treePath.table ;
				q.push( ed.fireGetADQL());
				// if constraint not applied to the queried table: join
				if( tt != st ) {
					for(var i=0 ;  i<this.joinKeys.length ; i++ ) {
						var lt = this.joinKeys[i].target_datatreepath.schema + "." + this.joinKeys[i].target_datatreepath.table;
						if( lt == tt) {
							joins[tt] = this.joinKeys[i];
						}
					} 
				}
			}
			if( this.orderBy != "" ) {
				var fs = this.orderBy.split('.');
				var tt = fs[0] + "." + fs[1] ;
				if( tt != st ) {
					for(var i=0 ;  i<this.joinKeys.length ; i++ ) {
						var lt = this.joinKeys[i].target_datatreepath.schema + "." + this.joinKeys[i].target_datatreepath.table;
						if( lt == tt) {
							joins[tt] = this.joinKeys[i];
						}
					} 
				}
			}
			if( q.length == 0 ) {
				q = ["*"];
			}
			$.each(this.listeners, function(i){
				that.listeners[i].controlUpdateQuery(q, joins);
			});
		}
	}
});

function tapQEditor_Mvc(){
	tapColSelector_Mvc.call(this);
};
/**
 * 
 */
tapQEditor_Mvc.prototype = Object.create(tapColSelector_Mvc.prototype, {
	processAttributeHandlerEvent : {
		value: function(ah, constListId){
			var that = this;
			var first = true;
			var currentTreePath = null;
			for( k in this.editors ) {
				first = false;
				break;
			}
			$.each(this.listeners, function(i){
				currentTreePath  = that.listeners[i].controlCurrentTreePath();
			});

			var divKey = this.constEditorRootId + ah.nameattr + this.const_key;
			Out.debug("mv constraint " + ah.nameattr + " to #" + this.constListId);
			var v = new TapKWConstraint_mVc({divId: divKey
				, constListId: constListId
				, isFirst: first
				, attributeHandler: ah
				, editorModel: this
				, defValue: ''
					, treePath: jQuery.extend({}, currentTreePath)});
			this.editors[divKey] = v;
			v.fireInit();
			this.const_key++;
		}
	},
	processInputCoord: {
		value: function(ra, dec, radius, frame, rakw, deckw, constListId) {
			var that = this;
			var first = true;
			var currentTreePath = null;
			for( k in this.editors ) {
				first = false;
				break;
			}
			$.each(this.listeners, function(i){
				currentTreePath  = that.listeners[i].controlCurrentTreePath();
			});
			var defValue = '';
			var nameAh = '';
			if( ra.startsWith('poslist:')) {
				defValue = 'list params,'+ radius;
				nameAh = 'POSLIST:' + ra.replace('poslist:','');;
			} else {
				defValue = ra + ',' + dec + ','+ radius;
				nameAh = 'POSITION';				
			}
			var divKey = this.constEditorRootId + "ADQLPos" + this.const_key;
			var v = new TapKWConstraint_mVc({divId: divKey
				, constListId:  constListId
				, isFirst: first
				, editorModel : this
				, attributeHandler: {nameattr: nameAh
					, nameorg: nameAh
					, "type" : "ADQLPos"
						, "ucd" : "adql.coor.columns"
							, "utype" : ""
								, "unit" : "deg"
									, "description" :  rakw + " " + deckw}
			, defValue: defValue
			, treePath: jQuery.extend({}, currentTreePath)});
			this.editors[divKey] = v;
			v.fireInit();
			this.const_key++;			
		}
	},
	updateQuery : {
		value: function() {
			var that = this;
			var retour= "    ";
			// queried table path
			var st = this.dataTreePath.schema + "." + this.dataTreePath.table;
			var joins = new Array();
			/*
			 * Merge all constraints
			 */
			for( var e in this.editors) {
				var ed = this.editors[e];
				var q =  ed.fireGetADQL();
				if( retour.length > 96 ) retour += "\n    ";
				if( q != null ) {
					retour += this.editors[e].fireGetADQL() + ' ';
					// Path of the table targeted by he current constraint
					var tt = ed.treePath.schema + "." + ed.treePath.table;
					// if constraint not applied to the queried table: join
					if( tt != st ) {
						for(var i=0 ;  i<this.joinKeys.length ; i++ ) {
							var lt = this.joinKeys[i].target_datatreepath.schema + "." + this.joinKeys[i].target_datatreepath.table;
							if( lt == tt) {
								joins[tt] = this.joinKeys[i];
							}
						}
						// if the targetted table is an uploaded file: crosmatch 	
					} else if( ed.fieldName.startsWith("POSLIST") ) {
						joins[tt] = {target_table: "upload." + ed.fieldName.replace('POSLIST:',''), target_column: "", source_column: ""};
					}
				}
			}
			if( this.orderBy != "" ) {
				var fs = this.orderBy.split('.');
				var tt = fs[0] + "." + fs[1] ;
				if( tt != st ) {
					for(var i=0 ;  i<this.joinKeys.length ; i++ ) {
						var lt = this.joinKeys[i].target_datatreepath.schema + "." + this.joinKeys[i].target_datatreepath.table;
						if( lt == tt) {
							joins[tt] = this.joinKeys[i];
						}
					} 
				}
			}
			$.each(this.listeners, function(i){
				that.listeners[i].controlUpdateQuery(retour, joins);
			});
		}
	}
});

console.log('=============== >  ConstQEditor_m.js ');

/**
 * ConstQEditor_mVc: View of the constraint query editor
 * 
 * @param parentDivId : ID of the div containing the query editor
 * @param formName    : Name of the form. Although internal use must be 
 *                      set from outside to avoi conflict by JQuery selectors 
 * @param queryview   : JS object supposed to take the constraint locally edited.
 *                      It supposed to have a public method names fireConstQuery(const)
 *                      where const is an object like {columnConst:"SQL stmt", orderConst:"att name"}
 *                     
 * @returns {ConstQEditor_mVc}
 */
function ConstQEditor_mVc(params /*{parentDivId,formName,queryView}*/){
	var that = this;
	this.fieldsLoaded = false;
	this.dataTreePath = null; // instance of DataTreePath
	/**
	 * who is listening to us?
	 */
	this.listeners = new Array();
	this.queryView = params.queryView;
	/**
	 * DOM references
	 */
	this.parentDiv = $("#" + params.parentDivId );
	this.constContainerId   = params.parentDivId + "_constcont";
	this.constListId   = '';
	this.formName = params.formName;
	this.orderById     = params.parentDivId + "_orderby";
	this.fieldListView = new FieldList_mVc(params.parentDivId
			, this.formName
			, {stackHandler: function(ahName){ that.fireAttributeEvent(ahName);}
	, orderByHandler: function(ahName){ that.fireOrderBy(ahName);}
	}
	);
	this.constListView = new ConstList_mVc(params.parentDivId
			, this.formName
			, this.constContainerId
			, function(ahName){ that.fireClearAllConst();}
	);
	this.orderByView = new 	OrderBy_mVc(params.parentDivId
			, this.formName
			, this.constContainerId
			, function(ahName){ that.fireOrderBy(ahName);}
	);
};

ConstQEditor_mVc.prototype = {
		/**
		 * Instanciate components
		 */
		/**
		 * add a listener to this view
		 */
		addListener : function(list){
			this.listeners.push(list);
		},
		draw : function() {
			this.fieldListView.draw();
			this.parentDiv.append('<div id=' + this.constContainerId + ' style="width: 450px;float: left;background: transparent; display: inline;"></div>');
			this.constListId = this.constListView.draw();
			this.orderByView.draw();
		},	
		fireSetTreepath: function(dataTreePath){
			this.fieldListView.setStackTooltip("Click to select this field");
			var that = this;
			this.dataTreePath = dataTreePath;	
			this.fieldListView.setDataTreePath(this.dataTreePath);
			$.each(this.listeners, function(i){
				that.listeners[i].controlLoadFields(that.dataTreePath);
			});
		},
		fireOrderBy : function(nameattr){
			if( nameattr != 'OrderBy' ) {
				this.orderByView.setOrderBy(nameattr);
				var stmt = nameattr ;
				if( this.orderByView.isDesc()) stmt += " desc";
				if( this.queryView != null ) {				
					this.queryView.fireAddConstraint(this.formName, "orderby", [stmt]);
				} else {
					Out.info("No Query View OB:" + stmt);
				}
			} else {
				this.queryView.fireDelConstraint(this.formName, "orderby");
			}
		},
		fireAttributeEvent : function(ahname){
			var that = this;
			$.each(this.listeners, function(i){
				that.listeners[i].controlAttributeEvent(ahname, that.constListId);
			});
			$("#" + this.constListId + " span.help").attr("style","display:none;");
		},
		fireClearAllConst : function() {
			this.constListView.fireClearAllConst();
			var that = this;
			$.each(this.listeners, function(i){
				that.listeners[i].controlClearAllConst();
			});		
		},
		fireClearConst : function(filter) {
			this.constListView.fireClearConst(filter);
			var that = this;
			$.each(this.listeners, function(i){
				that.listeners[i].controlClearConst(filter);
			});		
		},
		fireGetNumberOfEditor : function() {
			var that = this;
			var retour = 0;
			$.each(this.listeners, function(i){
				retour =  that.listeners[i].controlGetNumberOfEditor();
			});		
			return retour;
		},
		printTypoMsg : function(fault, msg){
			this.constListView.printTypoMsg(fault, msg);
		},
		isTypoGreen : function(){
			return constListView.printTypoMsg(fault, msg);
		},
		updateQuery : function(consts) {	
			if( this.queryView != null ) {
				this.queryView.fireAddConstraint(this.formName, "column", consts);
			} else {
				Out.info("No Query View:" + JSON.stringify(consts));
			}
		},
		runQuery : function() {	
			if( this.queryView != null ) {
				this.queryView.runQuery();
			} else {
				Out.info("No Query View");
			}
		}, 
		getAttributeHandlers: function(){
			var that = this;
			var retour = 0;
			$.each(this.listeners, function(i){
				retour =  that.listeners[i].controlGetAttributeHandlers();
			});		
			return retour;
		},
		/*
		 * The purpose of the default value depends on the context
		 * Just used for regions right now
		 */
		getDefaultValue: function(){
			var that = this;
			var retour = 0;
			$.each(this.listeners, function(i){
				retour =  that.listeners[i].controlGetDefaultValue();
			});		
			return retour;
		},
		fieldsStored: function(ahs){
			this.fieldListView.displayFields(ahs);
		}
};

/**
 * Subclass of ConstQEditor_mVc handling the edition of UCD bases constraints
 * @param parentDivId
 * @param formName
 * @param queryview
 * @returns {UcdQEditor_mVc}
 */
function UcdQEditor_mVc(params /*{parentDivId,formName,queryView, help}*/){
	ConstQEditor_mVc.call(this, params);
	var that = this;
	this.help = params.help;
	this.fieldListView = new UcdFieldList_mVc(params.parentDivId
			, this.formName
			, { stackHandler: function(ahName){ that.fireAttributeEvent(ahName);}
	, orderByHandler: function(ahName){ that.fireOrderBy(ahName);}
	}
	);
};

/**
 * Method overloading
 */
UcdQEditor_mVc.prototype = Object.create(ConstQEditor_mVc.prototype, {	
	draw : { 
		value: function() {
			this.fieldListView.draw();
			this.parentDiv.append('<div id=' + this.constContainerId + ' style="background: transparent; width:450px;float: left;"></div>');
			this.constListId = this.constListView.draw();
			if( this.help != undefined)
				$('#' + this.constContainerId).append('<div style="width: 100%;"><span class=spanhelp>' + this.help + '</span></div>');
		}
	},		
	updateQuery : { 
		value:  function(consts) {	
			if( this.queryView != null ) {
				this.queryView.fireAddConstraint(this.formName, "ucd", consts);
			} else {
				Out.info("No Query View:" + JSON.stringify(consts));
			}
		}
	}
});

/**
 * Subclass of UcdQEditor_mVc handling the edition of UCD bases constraints in matchPattern
 * @param parentDivId
 * @param formName
 * @param queryview
 * @returns {UcdQEditor_mVc}
 */
function UcdPatternEditor_mVc(params /*{parentDivId, formName, queryView, help}*/){
	UcdQEditor_mVc.call(this, params);
};
/**
 * Method overloading
 */
UcdPatternEditor_mVc.prototype = Object.create(UcdQEditor_mVc.prototype, {	
	updateQuery : { 
		value:  function(consts) {	
			if( this.queryView != null ) {
				this.queryView.fireAddConstraint(this.formName, "relation", consts);
			} else {
				Out.info("No Query View:" + JSON.stringify(consts));
			}
		}
	}
});

/**
 * Subclass of ConstQEditor_mVc handling the edition of position based constraints
 * @param parentDivId
 * @param formName
 * @param queryview
 * @returns {UcdQEditor_mVc}
 */
function PosQEditor_mVc(params /*{parentDivId, formName, queryView, frames, urls}*/){
	ConstQEditor_mVc.call(this, params);
	params.editor = this;
	this.fieldListView = new ConeSearch_mVc(params);
	console.log("1");
};
/**
 * Method overloading
 */
PosQEditor_mVc.prototype = Object.create(ConstQEditor_mVc.prototype, {	
	draw : { 
		value: function() {
			var that = this;
			this.fieldListView.draw();
			this.parentDiv.append('<div  style="width: 25px; height: 80px;float: left;background: transparent; padding-left: 10px;padding-top: 30px; display: inline;"><input  title="Click to add a search cone" class="stackconstbutton" type="button"></div>');
			this.parentDiv.append('<div id=' + this.constContainerId + ' style="float: left;background: transparent; display: inline;"></div>');
			this.constListId = this.constListView.draw();
			this.parentDiv.find("input.stackconstbutton").click(function() {that.fireAttributeEvent();});
			this.fieldListView.setRegionForm(
					function(data){
						if( data.userAction ){
							if( data.region.size.x > 5 || data.region.size.y > 5) {
									Modalinfo.error("The region size can't exceeded 5 deg. \nIts actual size is " + JSON.stringify(data.region.size));
							} else { 
								that.fireRegionEvent(data);				
								Modalinfo.closeRegion();
							}
						}
					});
		}
	},
	displayFields: { 
		value: function() {}
	},
	updateQuery : { 
		value:  function(consts) {	
			if( this.queryView != null ) {
				this.queryView.fireAddConstraint(this.formName, "position", consts);
			} else {
				Out.info("No Query View:" + JSON.stringify(consts));
			}
		}
	},
	fireRegionEvent: { 
		value: function(data) {		
			var that = this;
			if( data && data.userAction && data.isReady ) {
				var rq = '';
				if( data.region.format == "array2dim") {
					rq = '';
					for( var i=0 ; i<(data.region.points.length - 1) ; i++ ) {
						if( i > 0 ) rq += " ";
						rq += data.region.points[i][0] + " " + data.region.points[i][1];
					}

				} else if( data.region.format == "array") {
					rq = '';
					for( var i=0 ; i<data.region.points.length  ; i++ ) {
						if( i > 0 ) rq += " ";
						rq += data.region.points[i];
					}

				} else {
					Modalinfo.error(data.region.format + " not supported region format");
					return;
				}
				/*
				 * In region mode: only one constraint
				 */
				this.fireClearAllConst();
				console.log(rq);
				if( rq != '' ) {
					$.each(this.listeners, function(i){
						that.listeners[i].controlAttributeEvent({type: "region", frame: "ICRS", position: rq, radius: 0}, that.constListId);
					});
					$("#" + this.constListId + " span.help").attr("style","display:none;");
				}
				this.fieldListView.resetPosition();
			}
		}
	},
	fireAttributeEvent: { 
		value: function() {			
			var that = this;
			$.each(this.listeners, function(i){
				that.listeners[i].controlClearConst(".*region.*");
			});		

			$.each(this.listeners, function(i){
				that.listeners[i].controlAttributeEvent(that.fieldListView.getSearchParameters(), that.constListId);
			});
			$("#" + this.constListId + " span.help").attr("style","display:none;");
			this.fieldListView.resetPosition();
		}
	},
	fireAttributeAutoEvent: { 
		value: function() {			
			var that = this;
			/*
			 * More than One editor mean that he users have stacked multiple position constraints
			 */
			if( this.fireGetNumberOfEditor() <= 1 && this.fieldListView.hasSearchParameters() ) {
				this.fireClearAllConst();
				$.each(this.listeners, function(i){
					that.listeners[i].controlAttributeEvent(that.fieldListView.getSearchParameters(), that.constListId);
				});
				$("#" + this.constListId + " span.help").attr("style","display:none;");
			}
		}
	},
	firePoslistUpload: { 
		value: function(filename) {			
			var that = this;
			this.constListView.fireRemoveAllHandler();
			$.each(this.listeners, function(i){
				that.listeners[i].controlAttributeEvent({position: "poslist:" + filename, radius: 0, frame: 'ICRS'}, that.constListId);
			});
			$("#" + this.constListId + " span.help").attr("style","display:none;");
		}
	}
});

/**
 * Subclass of CatalogueQEditor_mVc handling the edition of catalogue based constraints in matchPattern
 */
function CatalogueQEditor_mVc(params /*{parentDivId, formName, getMetaUrl, queryView, relationName, distanceQualifer, help}*/){
	ConstQEditor_mVc.call(this, params);
	var that = this;
	this.help = params.help;
	this.fieldListView = new CatalogueList_mVc(params.parentDivId
			, this.formName
			, {stackHandler: function(ahName){ that.fireAttributeEvent(ahName);}
	}
	);
};
/**
 * Method overloading
 */
CatalogueQEditor_mVc.prototype = Object.create(ConstQEditor_mVc.prototype, {	
	draw : { 
		value: function() {
			var that = this;
			this.fieldListView.draw();
			this.parentDiv.append('<div id=' + this.constContainerId + ' style="width:450px;float: left;background: transparent; display: inline;"></div>');
			this.constListId = this.constListView.draw();
			$('#' + this.constContainerId).append('<div style="width: 100%;"><span class=spanhelp>' + this.help + '</span></div>');
			if( !this.fieldsLoaded ) {
				$.each(this.listeners, function(i){
					that.listeners[i].controlLoadFields();
					this.fieldsLoaded = true;
				});
			}
		}
	},		
	updateQuery : { 
		value:  function(consts) {	
			if( this.queryView != null ) {
				this.queryView.fireAddConstraint(this.formName, "relation", consts);
			} else {
				Out.info("No Query View:" + JSON.stringify(consts));
			}
		}
	}
});


/**
 * Subclass of tapQEditor_mVc handling the edition of tap queries
 */
function tapColSelector_mVc(params){
	ConstQEditor_mVc.call(this, params);
	var that = this;
	this.help = params.help;
	this.constPosContainer   = params.parentDivId + "_constposcont";
	this.fieldListView = new TapColList_mVc(params.parentDivId
			, this.formName
			, {stackHandler: function(ahName){ that.fireAttributeEvent(ahName);}
	, orderByHandler: function(ahName){ that.fireOrderBy(ahName);}
	}	        
	, params.sessionID
	);
	this.fieldListView.setStackTooltip("Click to select this field");
};
/**
 * Method overloading
 */
tapColSelector_mVc.prototype = Object.create(ConstQEditor_mVc.prototype, {	
	draw : { 
		value: function() {
			this.fieldListView.draw();
			this.parentDiv.append('<div  class="editorrightpart">'
					//	+ '<div id=' + this.constPosContainer + '></div>'
					+ '<div id=' + this.constContainerId + ' style="position: absolute;top: 0px"></div>'
					+ '</div>');


			this.constListId = this.constListView.draw();
			this.orderByView.draw();
		}
	},
	// handler is called after the form is uptdated: query submission e.g.
	fireSetTreepath : { 
		value: function(treePath, handler){
			var that = this;
			if( this.dataTreePath != null ){
				this.fireClearAllConst();
				this.queryView.fireDelConstraint(this.formName, "orderby");
			}

			this.dataTreePath = treePath;	
			// table name can include the schema
			//this.dataTreePath.table = this.dataTreePath.table.split('.').pop();
			this.queryView.fireSetTreePath(this.dataTreePath);
			this.queryView.fireAddConstraint(this.formName, "select", ["*"]);
			this.orderByView.fireClearAllConst();
			$.each(this.listeners, function(i){
				that.listeners[i].controlLoadFields(that.dataTreePath, handler);
			});
			this.fieldListView.setDataTreePath(this.dataTreePath);
		}
	},
	addTableOption: { 
		value: function(treePath) {
			this.fieldListView.addTableOption(treePath);
		}
	},
	fireAttributeEvent :  { 
		value: function(ahname){
			var that = this;
			$.each(this.listeners, function(i){
				that.listeners[i].controlAttributeHandlerEvent(that.fieldListView.getAttributeHandler(ahname), that.constListId);
			});
			$("#" + this.constListId + " span.help").attr("style","display:none;");
		}
	},		
	fireOrderBy :  { 
		value : function(nameattr){
			var tpn ="";
			if( nameattr != 'OrderBy' ) {
				tpn = this.fieldListView.dataTreePath.schema + "." + this.fieldListView.dataTreePath.table + "." + nameattr;
				var qtpn = tpn.quotedTableName();
				this.orderByView.setOrderBy(tpn);
				var stmt = nameattr ;
				if( this.orderByView.isDesc()) qtpn += " desc";
				if( this.queryView != null ) {				
					this.queryView.fireAddConstraint(this.formName, "orderby" , [qtpn]);
				} else {
					Out.info("No Query View OB:" + stmt);
				}
			} else {
				this.queryView.fireDelConstraint(this.formName, "orderby");
			}
			var that = this;
			$.each(this.listeners, function(i){
				that.listeners[i].controlOrderBy(tpn);
			});

		}
	},
	updateQuery  :  { 
		value:function(consts, joins) {	
			if( this.queryView != null ) {
				this.queryView.fireAddConstraint(this.formName, "select", consts, joins);
			} else {
				Out.info("No Query View:" + JSON.stringify(consts));
			}
		}
	},
	getCurrentTreePath: {
		value: function(){
			return this.fieldListView.dataTreePath;
		}
	}

});


/**
 * @param parentDivId
 * @param formName
 * @param queryview
 * @param getTableAttUrl
 * @param sesameUrl
 * @param upload: {url,  postHandler: called on success}
 * @param sessionID
 * @param help
 * @returns {tapQEditor_mVc}
 */
function tapQEditor_mVc(params /*parentDivId, formName, sesameUrl, upload { url, postHandler}, queryView, currentNode }*/){
	tapColSelector_mVc.call(this, params);
	var that = this;
	this.fieldListView = new TapFieldList_mVc(params.parentDivId
			, this.formName
			, {stackHandler: function(ahName){ that.fireAttributeEvent(ahName);}
	, orderByHandler: function(ahName){ that.fireOrderBy(ahName);}
	, raHandler: null
	, decHandler: null}	        
	, params.sessionID
	);
	this.fieldListView.setStackTooltip("Click to constrain this field");
	this.posEditor = new TapSimplePos_mVc({editor: this
		, parentDivId: this.constPosContainer
		, formName: this.formName
		, frames: null
		, urls: {sesameURL: params.sesameUrl, uploadURL:params.upload.url}
	, postUploadHandler: params.upload.postHandler
	});
};
/**
 * Method overloading
 */
tapQEditor_mVc.prototype = Object.create(tapColSelector_mVc.prototype, {	
	draw : { 
		value: function() {
			this.fieldListView.draw();
			this.parentDiv.append('<div  class="editorrightpart">'
					+ '<div id=' + this.constPosContainer + '></div>'
					+ '<div id=' + this.constContainerId + ' style="position: absolute;top: 55px"></div>'
					+ '</div>');


//			$("#" +  this.constContainerId).append('<div class=constdiv><fieldset class="constraintlist">'
//			+ '<legend>Position</legend>'
//			+ '<span class=help>Click on a <input class="stackconstbutton" type="button"> button to append,<br>the constraint to the list</span>'
//			+ '</fieldset>'
//			+ '</div>');
			this.posEditor.draw();
			this.constListId = this.constListView.draw();
			this.orderByView.draw();
		}
	},
	displayFields: { 
		value: function(attributesHandlers){
			this.fieldListView.displayFields(attributesHandlers);
			this.fieldListView.lookForAlphaKeyword();
			this.fieldListView.lookForDeltaKeyword();
			return;
		}
	},
	fireAttributeEvent :  { 
		value: function(ahname){
			var that = this;
			$.each(this.listeners, function(i){
				that.listeners[i].controlAttributeHandlerEvent(that.fieldListView.getAttributeHandler(ahname), that.constListId);
			});
			$("#" + this.constListId + " span.help").attr("style","display:none;");
		}
	},		
	fireInputCoordEvent :  { 
		value : function(ra, dec, radius, frame){
			var that = this;
			var rakw = this.fieldListView.getRaKeyword();
			if( rakw.length == '' ) {
				Modalinfo.error("RA field not set");
				return;
			}
			var deckw = this.fieldListView.getDeltaKeyword();
			if( deckw.length == '' ) {
				Modalinfo.error("DEC field not set");
				return;
			}
			$.each(this.listeners, function(i){
				that.listeners[i].controlInputCoord(ra, dec, radius, frame
						, rakw, deckw, that.constListId);
			});
			$("#" + this.constListId + " span.help").attr("style","display:none;");
		}
	},
	updateQuery  :  { 
		value:function(consts, joins) {	
			if( this.queryView != null ) {
				this.queryView.fireAddConstraint(this.formName, "column", consts, joins);
			} else {
				Out.info("No Query View:" + JSON.stringify(consts));
			}
		}
	},
	isReadyToUpload :  { 
		value:function(consts, joins) {	
			if( this.fieldListView.getRaKeyword().length == '' || this.fieldListView.getDeltaKeyword() == '' ) {
				Modalinfo.error("Both RA and DEC fields must be set");
				return false;
			}
			return true;
		}
	},
	getUploadedFile: {
		value: function(){
			return this.posEditor.uploadedFile;
		}
	}
});


console.log('=============== >  ConstQEditor_v.js ');

ConstQEditor_mvC = function(view,model){
	/**
	 * listen to the view
	 */

	var vlist = {
			controlLoadFields: function(treePath, handler){
				model.loadFields(treePath, handler);
			},
			controlAttributeEvent: function(ahname, constListId){
				model.processAttributeEvent(ahname, constListId);
			},
			controlAttributeHandlerEvent: function(ah, constListId){
				model.processAttributeHandlerEvent(ah, constListId);
			},
			controlClearAllConst : function() {
				model.clearAllConst();
			},
			controlClearConst : function(filter) {
				model.clearConst(filter);
			},
			controlGetNumberOfEditor : function() {
				return model.getNumberOfEditor();
			},
			controlInputCoord: function(ra, dec, radius, frame, rakw, deckw, constListId) {
				model.processInputCoord(ra, dec, radius, frame, rakw, deckw, constListId);
			},
			controlOrderBy: function(nameattr) {
				model.processOrderBy(nameattr);
			},
			controlGetAttributeHandlers: function() {
				return model.getAttributeHandlers();
			},
			controlGetDefaultValue: function() {
				return model.getDefaultValue();
			}
	};

	view.addListener(vlist);

	var mlist = {
			nextListener: function(){
				view.nextListener();
			}, 
			controlTypoMsg : function(fault, msg) {
				view.printTypoMsg(fault, msg);
			},
			controlTypoGreen : function() {
				return view.isTypoGreen();
			},
			controlUpdateQuery : function(consts, joins) {
				view.updateQuery(consts, joins);
			},
			controlRunQuery : function(consts) {
				view.runQuery(consts);
			},
			controlAddTableOption: function(treePath) {
				view.addTableOption(treePath);
			},
			controlCurrentTreePath: function() {
				return view.getCurrentTreePath();
			}, 
			controlFieldsStored: function(attributeHandlers) {
				view.fieldsStored(attributeHandlers);
			}
	};

	model.addListener(mlist);
};

console.log('=============== >  ConstQEditor_c.js ');

/**
 * @returns {QueryTextEditor_Mvc}
 */
function QueryTextEditor_Mvc() {
	this.colConst =  new Array();
	this.obConst  = new Array();
	this.ucdConst = new Array();
	this.posConst = new Array();
	this.relConst = new Array();
	this.kwConst  = new Array();
	this.limConst = "";
	this.query = "";
	this.treePath = null;
	this.listener = null;
};

QueryTextEditor_Mvc.prototype = {
		addListener : function(list){
			this.listener = list;
		},
		getQuery: function() {
			return this.query;
		},
		processAddConstraint: function(label, type, constraints) {
			Out.debug("add constraint of type " + type + " from form " + label);
			if( type == "column") {
				this.delConst(label, this.colConst);
				this.addConstraintToArray(label, constraints, this.colConst) ;
			} else if( type == "orderby" && constraints.length > 0) {
				this.obConst = constraints[0];				
			} else if( type == "ucd") {
				this.delConst(label, this.ucdConst);
				this.addConstraintToArray(label, constraints,this. ucdConst) ;
			} else if( type == "position") {
				this.delConst(label,this.posConst);
				this.addConstraintToArray(label, constraints, this.posConst) ;
			} else if( type == "relation") {
				this.delConst(label, this.relConst);
				this.addConstraintToArray(label, constraints, this.relConst) ;
			} else if( type == "kwconst") {
				this.delConst(label, this.kwConst);
				this.addConstraintToArray(label, constraints, this.kwConst) ;
			} else if( type == "limit" && constraints.length > 0) {
				this.limConst = constraints[0];				
			} else {
				Modalinfo.error("QueryTextEditor do not know what to do with a constraint typed as " +type);
			}		
			this.buildQuery();
			this.notifyDisplayQuery();
		},
		addConstraintToArray: function(label, constraints, constArray) {
			if(constraints != null &&  $.trim(constraints) != "" ) {
				constArray.push({label:label, constraints: constraints});
			}
		},
		processDelConstraint: function(label, type) {
			Out.debug("Del constraint of type " + type + " from form " + label);
			if( type == "column") {
				this.delConst(label, this.colConst);
			} else if( type == "orderby") {
				this.obConst = "";				
			} else if( type == "ucd") {
				this.delConst(label, this.ucdConst);
			} else if( type == "position") {
				this.delConst(label, this.posConst);
			} else if( type == "relation") {
				this.delConst(label, this.relConst);
			} else if( type == "kwconst") {
				this.delConst(label, this.kwConst);
			} else if( type == "limit") {
				this.limConst = "";												
			} else {
				Modalinfo.error("QueryTextEditor do not know what to do with a constraint typed as " + type);
			}	
			this.buildQuery();
			this.notifyDisplayQuery();
		},
		delConst: function(label, constArray) {
			for( var i=0 ; i<constArray.length ; i++ ) {
				if( constArray[i].label == label){
					constArray.splice(i,1);
					return;
				}
			}
		},
		buildQuery: function() {
			this.query =  this.buildWhereConstraint("\nWherePosition"         , ","  , this.posConst)
			+ this.buildWhereConstraint("\nWhereAttributeSaada"   , "AND", this.colConst)
			+ this.buildWhereConstraint("\nWhereUCD"              , "AND", this.ucdConst)
			+ this.buildWhereConstraint("\nWhereRelation"         , " "  , this.relConst)
			+ this.buildWhereConstraint("\nHavingCounterpartsWith", ","  , this.kwConst);
			if( this.obConst != "" ){
				this.query += "\nOrder By " + this.obConst + "\n";
			}
			if( this.limConst != "" && !isNaN(this.limConst) ){
				this.query += "\nLimit " + this.limConst + "\n";
			}
		},
		buildWhereConstraint : function(stmt, logical, constArray){
			var constrt ="";		
			var openPar = '';
			var closePar = '';
			if( logical == 'AND' ) {
				openPar = '(';
				closePar = ')';
			}
			for( var i=0 ; i<constArray.length ; i++ ){
				if( i > 0 ) {
					constrt += (logical.trim() == "") ?"\n":  ("\n    " + logical + "\n") ;
				}
				var tc = $.trim(constArray[i].constraints);
				constrt += (constArray.length > 1 )? ("    " +  openPar + tc + closePar):  constArray[i].constraints;
			}
			return (constrt == "")? "" : stmt + " {\n" + constrt + "\n}\n";
		},
		notifyDisplayQuery:  function() {
			this.listener.controlDisplayQuery(this.query);
		},
		setTreePath: function(treePath){
			this.treePath = $.extend({},treePath);
		}
};

/**
 * Subclass of QueryTextEditor_Mvc handling adql queries
 */
function ADQLTextEditor_Mvc(){
	QueryTextEditor_Mvc.call(this);
	this.selectConst = new Array();
	this.joinedTables = new Array();
	this.flatJoined = new Array();
	/*
	 * 
	 * {tablename -> {joinKey [select][orderby][constrainst]}
	 * joinKey {source_table, source_column, target_table, target_column}
	 */
	this.tableConst = new Array();

};
/**
 * Method overloading
 */
ADQLTextEditor_Mvc.prototype = Object.create(QueryTextEditor_Mvc.prototype, {	
	processAddConstraint: {
		value: function(label, type, constraints, tableJoin) {
			Out.debug("add constraint of type " + type + " from form " + label);
			if( type == "select") {
				this.selectConst = [{label:label, constraints: constraints}];
			} else if( type == "column") {
				this.delConst(label, this.colConst);
				this.addConstraintToArray(label, constraints, this.colConst) ;
			} else if( type == "orderby" && constraints.length > 0) {
				this.obConst = [{label:label, constraints: constraints[0]}];
			} else if( type == "kwconst") {
				this.delConst(label, this.kwConst);
				this.addConstraintToArray(label, constraints, this.kwConst) ;
			} else if( type == "limit" && constraints.length > 0) {
				this.limConst = {label:label, constraints: constraints[0]};;				
			} else {
				Modalinfo.error("QueryTextEditor do not know what to do with a constraint typed as " +type);
				return;
			}	
			this.joinedTables = new Array();
			this.flatJoined = new Array();
			if( tableJoin != null ) {
				for( var jt in tableJoin ) {
					var joinKey = tableJoin[jt];
					if( joinKey.target_column == "" ) {
						this.flatJoined.push(joinKey.target_table);
					} else {
						this.joinedTables[jt] = joinKey;
					}
				}
			}
			this.buildQuery();
			this.notifyDisplayQuery();
		}
	},

	getJoin : { 
		value: function() {
			var retour = "";
			for( var jt in this.joinedTables ) {
				var joinKey = this.joinedTables[jt];
				var tt = (joinKey.target_datatreepath.schema + "." + joinKey.target_datatreepath.table).quotedTableName();
				retour += "JOIN " + tt + " ON " 
				+ this.getCurrentTableName().quotedTableName() + "." +  joinKey.source_column.quotedTableName()
				+ " = " 
				+  tt + "." + joinKey.target_column.quotedTableName()  + "\n";		
			}
			return retour;
		}
	},

	buildQuery :{
		value: function() {
			var tableName = this.getCurrentTableName().quotedTableName();
			var li = this.limConst.constraints;
			var topLimit = ( li != undefined) ? ' TOP ' + li + ' ': '';
			this.query = "";
			this.query += this.buildWhereConstraint("SELECT " + topLimit , ",", this.selectConst);
			this.query += "FROM " + tableName ;
			if( this.flatJoined != null ){
				for( var i in this.flatJoined) {
					this.query += ", " + this.flatJoined[i];
				}
			}
			this.query += "\n";
			this.query += this.getJoin();			
			this.query += this.buildWhereConstraint("WHERE"   , "AND", this.colConst);
			this.query += this.buildWhereConstraint("ORDER BY", ","  , this.obConst);
		}
	},

	buildWhereConstraint : {
		value: function(stmt, logical, constArray){
			var constrt ="";		
			var openPar = '';
			var closePar = '';
			if( logical == 'AND' ) {
				openPar = '(';
				closePar = ')';
			}
			for( var i=0 ; i<constArray.length ; i++ ){
				if( i > 0 ) {
					constrt += (logical.trim() == "") ?"\n":  ("\n    " + logical + "\n") ;
				}
				var tc = $.trim(constArray[i].constraints) ;
				constrt += (constArray.length > 1 )? "    " +  openPar + tc + closePar:  constArray[i].constraints;
			}
			return (constrt == "")? "" : stmt + " " + constrt + "\n";
		}
	},

	getCurrentTableName: {
		value: function(){
			return this.treePath.schema + "." + this.treePath.table;
		}
	}
//	quoteTableName : {
//		value: function(tableName){
//			var regex = /([^.]*)\.(.*)/;
//			var results = regex.exec(tableName);
//			var table, schema;
//			if(!results){
//				table = tableName;
//				schema = "";
//			} else if( results.length == 2 ) {
//				table = results[1]; 
//				schema = "";
//			} else  {
//				table =  results[2];  
//				schema = results[1] + ".";
//			}
//			if( table.match(/^[a-zA-Z0-9][a-zA-Z0-9_]*$/ ) ){
//				return schema + table;
//			} else {
//				return schema + '"' + table +'"';
//			}
//		}
//	}
});


console.log('=============== >  QueryTextEditor_m.js ');


/**
 * @param params { parentDivId: 'query_div',defaultQuery}
 * @returns
 */
function QueryTextEditor_mVc(params) {
	this.parentDiv = $("#" +params.parentDivId );
	this.textareaid = params.parentDivId + "_text";
	this.defaultQuery = (params.defaultQuery.endsWith("\n"))? params.defaultQuery: params.defaultQuery + "";
	
	this.listener = null;
}
QueryTextEditor_mVc.prototype = {

		addListener : function(list){
			this.listener = list;
		},
		draw : function(){
			this.parentDiv.html('<textarea id="' + this.textareaid + '" class="querytext" id="Catalogue"></textarea>');
			this.displayQuery("");
		},
		fireGetQuery: function() {
			return $("#" + this.textareaid ).val();
		},		
		/*
		 *	Params: {type, constraints}
		 *	where supported typed are "column" "orderby" "ucd" "position" "relation" "limit"
		 *  Label is used to identify the form  constraints are coming from
		 */
		fireAddConstraint : function(label, type, constraints, joins) {
			this.listener.controlAddConstraint(label, type, constraints, joins);
		},
		fireDelConstraint : function(label, type) {
			this.listener.controlDelConstraint(label, type);
		},
		fireSetTreePath : function(treePath) {
			this.listener.controlSetTreePath(treePath);
		},
		displayQuery : function(query) {
			$("#" + this.textareaid ).val(this.defaultQuery + query);
		},
		getQuery : function(query) {
			return $("#" + this.textareaid ).val();
		},
		toggle: function() {
			this.parentDiv.toggle();
		}
};

console.log('=============== >  QueryTextEditor_v.js ');

QueryTextEditor_mvC = function(view, model){
	/**
	 * listen to the view
	 */
	var vlist = {
			controlGetQuery : function(){
				return model.getQuery();
			},
			controlAddConstraint : function(label, type, constraints, joins){
				model.processAddConstraint(label, type, constraints, joins);
			},
			controlDelConstraint : function(label, type){
				model.processDelConstraint(label, type);
			},
			controlSetTreePath: function(treePath){
				model.setTreePath(treePath);
			}
	};
	view.addListener(vlist);

	var mlist = {
			controlDisplayQuery : function(query){
				view.displayQuery(query);
			}
	};
	model.addListener(mlist);
};

console.log('=============== >  QueryTextEditor_c.js ');

function Segment(polygoneNodes /*canvas*/)
{
	var alfa;
	var beta;	
	var node = [];
	node = polygoneNodes;
	var nodesegmentos;

	this.IsCursorOn = function(x,y)
	{
		var result;
		
		//crear los segmentos:
		nodesegmentos = NumSegment(node);
		
		//si es un rectangulo
		if(seg = IsRectangle(x,y))
		{
			//calcular la distancia
			result = Distance(seg,x,y);
			return result;
		}			
			
	};
	
	//funcion para saber si se crea el rectangulo
	function IsRectangle(coorx, coory)	
	{	
		
		var x = parseInt(coorx);
		var y = parseInt(coory);
		var nodeXtremity = {};			
		
		var xa,xb,ya,yb;				
		
		for(var i in nodesegmentos)
		{				
			var xmin, xmax;
			var ymin, ymax;
			
			xa = node[nodesegmentos[i].A].cx;
			ya = node[nodesegmentos[i].A].cy;			
			xb = node[nodesegmentos[i].B].cx;
			yb = node[nodesegmentos[i].B].cy;
					
			xmin = (parseInt(xa) > parseInt(xb) )? xb:xa;
			xmax = (parseInt(xa) > parseInt(xb) )? xa:xb;
			
			ymin = (parseInt(ya) > parseInt(yb) )? yb:ya;
			ymax = (parseInt(ya) > parseInt(yb) )? ya:yb;

			if(x >= xmin && x <= xmax)
			{				
				if(y >= ymin && y <= ymax )
				{
					seg = {xA:xa, yA:ya, xB:xb, yB:yb, segmento:i};
					if( ( dis = Distance(seg,x,y) ) != -1)
					{
					return {xA:xa, yA:ya, xB:xb, yB:yb, segmento:i};	
					}
					
				}
			}
			if(xmax === xmin)
			{
				if(y >= ymin && y <= ymax )
				{
					seg = {xA:xa, yA:ya, xB:xb, yB:yb, segmento:i};
					if( ( dis = Distance(seg,x,y) ) != -1)
					{
					return {xA:xa, yA:ya, xB:xb, yB:yb, segmento:i};	
					}
				}
			}
			if(ymin === ymax)
			{	
				if(x > xmin && x < xmax)
				{
					seg = {xA:xa, yA:ya, xB:xb, yB:yb, segmento:i};
					if( ( dis = Distance(seg,x,y) ) != -1)
					{
					return {xA:xa, yA:ya, xB:xb, yB:yb, segmento:i};	
					}	
				}				
			}
		}	
	}

	//funcion para calcular la distancia del punto M(x,y) de los segmentos: A(xa,ya) y B(xb,yb)
	function Distance(seg,x,y)
	{
		//console.log('puedes calcular distancia');	
		var recta;
		var distancia;
		
		var h,v;
		
		if((v = Vertical(seg, x)) != -1)
		{
			return {flag: "vertical", segmento: seg};
			
		}else if((h = Horizontale(seg, y)) != -1)
		{		
			return {flag: "horizontal", segmento: seg};
		}
		else if(v == -1 && h == -1)
		{
			
			var alfa = CalculerAlfa(seg);
			var beta = Beta(seg);			
			
			recta = Math.abs( ( (alfa * parseInt(x)) + parseInt(y) + beta) );
			distancia = (recta / Math.sqrt(((alfa * alfa)+1)));
			
			//if(distancia <= 3 && distancia >= 0)
			if(distancia <= 2 && distancia >= 0)
			{
				return {flag: "distancia", segmento: seg, alfa: alfa, beta: beta};
			}
		}

		return -1;
		
	}
	
	//function para crear los segmentos a partir de los nodos
	function NumSegment(array)
	{
		var numsegmentos = []; //variable para almacenar el numero de segmentos
		var temp; //variable para contar los nodos "i"
		var segmentoini, segmentofin;
		
		//recorrer los nodos
		for(var i in array)
		{
			if(segmentoini == undefined)
			{
				segmentoini = i;
			}			
			else if(segmentofin == undefined)
			{
				segmentofin = i;
			}				
			
			//almacenar segmentos
			if(segmentoini != undefined && segmentofin != undefined)
			{
				numsegmentos.push
				({
					A: segmentoini,
					B: segmentofin
				});
				
				segmentoini = segmentofin;
				segmentofin = undefined;
			}
			
			if(parseInt(node.length - 1) == i)
			{				
				numsegmentos.push
				({
					A: (node.length -1),
					B: 0
				});
			}
			
		}				
		return numsegmentos;
	}
	
	//dibujar el segmento
	function DrawnLine()
	{
		context.beginPath();
		context.moveTo(125,158);
		context.lineTo(250,158);
	
		
		context.moveTo(250,158);		
		context.lineTo(250,100);
		
		context.moveTo(250,100);		
		context.lineTo(125,158);
		
		context.stroke();
		context.closePath();		
		
		for(var i in node)
		{
			context.beginPath();
			context.arc(node[i].cx,node[i].cy,5, 0, Math.PI * 2,true);
			context.fillStyle = "blue";
		    context.fill();
			context.stroke();
			context.closePath();
		}
	}
	
	//Obtener el valor alfa
	function CalculerAlfa(seg)
	{		
		alfa = -((seg.yB - seg.yA) / (seg.xB - seg.xA));
		
		return alfa;
	}
	
	//Obtener el valor beta
	function Beta(seg)
	{		
		beta = -(alfa *  seg.xA)- seg.yA;
		
		return beta;
	}
	
	//Calcular la distancia de un segmento horizontal
	function Horizontale(seg, y)
	{
		var horizontal;
		var coory = parseInt(y);
		
		horizontal = Math.abs(seg.yA - coory);
		
		if(horizontal <= 1 && horizontal >= 0)		
			return horizontal;
			else
				return -1;	
	}
	
	//Calcular la distancia de un segmento vertical
	function Vertical(seg, x)
	{
		var vertical;
		var coorx = parseInt(x);
		vertical =  Math.abs(seg.xA - coorx);				
		
		if(vertical <= 1 && vertical >= 0)		
		return vertical;
		else
			return -1;		
	}

	//intersertion de segments
	this.Itersection = function(nodeselected,status)
	{
		var numseg = NumSegment(node);
		var lastseg = numseg.length - 2;
		var firstnode = 0;
		var dx,dy;		
		var d=-1;
		
		nodeselected = parseInt(nodeselected);
		
		if(status === false)
		{
			
			if(numseg.length > 3)
			{
				if(nodeselected != 0)
				{
					xa1 = node[numseg[lastseg].A].cx;
					ya1 = node[numseg[lastseg].A].cy;			
					xb2 = node[numseg[lastseg].B].cx;
					yb2 = node[numseg[lastseg].B].cy;
					var nodenumberA =  parseInt(numseg[lastseg].A); 
					var nodenumberB =  parseInt(numseg[lastseg].B);
					
					for(var i in numseg)
					{					
						xa3 = node[numseg[i].A].cx;
						ya3 = node[numseg[i].A].cy;			
						xb4 = node[numseg[i].B].cx;
						yb4 = node[numseg[i].B].cy;						
						
						d = distance(xa1,ya1,xb2,yb2,xa3,ya3,xb4,yb4);									
						
						if( d != -1)
						{
							//  ((x3-x4)*(x1*y2-y1*x2)-(x1-x2)*(x3*y4-y3*x4))/d;
							dx = ((xa3-xb4)*(xa1*yb2-ya1*xb2)-(xa1-xb2)*(xa3*yb4-ya3*xb4))/d;
							//   ((y3-y4)*(x1*y2-y1*x2)-(y1-y2)*(x3*y4-y3*x4))/d;
							dy = ((ya3-yb4)*(xa1*yb2-ya1*xb2)-(ya1-yb2)*(xa3*yb4-ya3*xb4))/d; 
																																										
							var resultado = ResultadoSegmento(xa1,ya1,xb2,yb2,xa3,ya3,xb4,yb4 , dx , dy);									
							
							//si es diferente de nulo hay una interseccion
							if(resultado != -1)
							{
								if(i != (numseg.length -1))
								{
									if(xb4 != xa1 && yb4 != ya1)
									{
										//if(xa1 != xa3 && ya1 != ya3)
											return { x1:xa1, y1:ya1 , x2:xb2 , y2:yb2 , seginit:lastseg, segfin:i, nA:nodenumberA, nB:nodenumberB};
									}
										
								}													
							}					
						}
					
					}
				}
				else if(nodeselected === 0)
				{
					xa1 = node[numseg[firstnode].A].cx;
					ya1 = node[numseg[firstnode].A].cy;			
					xb2 = node[numseg[firstnode].B].cx;
					yb2 = node[numseg[firstnode].B].cy;
					var nodenumberA =  parseInt(numseg[firstnode].A); 
					var nodenumberB =  parseInt(numseg[firstnode].B);
					
					//invertir el orden de los segmentos
					numseg.reverse();
					
					for(var i in numseg)
					{												
						if(i != 0)
						{

							xa3 = node[numseg[i].A].cx;
							ya3 = node[numseg[i].A].cy;			
							xb4 = node[numseg[i].B].cx;
							yb4 = node[numseg[i].B].cy;						
							
							d = distance(xa1,ya1,xb2,yb2,xa3,ya3,xb4,yb4);									
							
							if( d != -1)
							{
								//  ((x3-x4)*(x1*y2-y1*x2)-(x1-x2)*(x3*y4-y3*x4))/d;
								dx = ((xa3-xb4)*(xa1*yb2-ya1*xb2)-(xa1-xb2)*(xa3*yb4-ya3*xb4))/d;
								//   ((y3-y4)*(x1*y2-y1*x2)-(y1-y2)*(x3*y4-y3*x4))/d;
								dy = ((ya3-yb4)*(xa1*yb2-ya1*xb2)-(ya1-yb2)*(xa3*yb4-ya3*xb4))/d; 
																																											
								var resultado = ResultadoSegmento(xa1,ya1,xb2,yb2,xa3,ya3,xb4,yb4 , dx , dy);									
								
								//si es diferente de -1 hay una interseccion
								if(resultado != -1)
								{
									if(i != (numseg.length -1))
									{
										if(xb2 != xa3 && yb2 != ya3)
										{
											return { x1:xa1, y1:ya1 , x2:xb2 , y2:yb2 , seginit:lastseg, segfin:i, nA:nodenumberA, nB:nodenumberB};
										}
											
									}													
								}					
							}
						}					
					
					}
				}
			}
		}
		else if(status)
		{
			var seg1 ={} , seg2 = {};
			var option;
			var resseg = [];
			
			if(numseg.length > 3)
			{				
				if(nodeselected === 0)
				{
					//segmento 1
					seg1.xA = node.length - 1;
					seg1.xB = nodeselected;
					//segmento 2
					seg2.xA = nodeselected;
					seg2.xB = nodeselected + 1;
				}
				else if(nodeselected === (node.length - 1) )
				{
					//segmento 1
					seg1.xA = nodeselected - 1;
					seg1.xB = nodeselected;
					//segmento 2
					seg2.xA = nodeselected;
					seg2.xB = 0;
				}
				else
				{
					//segmento 1
					seg1.xA = nodeselected - 1;
					seg1.xB = nodeselected;
					//segmento 2
					seg2.xA = nodeselected;
					seg2.xB = nodeselected + 1;
				}														
				
				for(var i in numseg)
				{																			
					if(parseInt(numseg[i].A) === seg1.xA && parseInt(numseg[i].B) == seg1.xB)
					{
						continue;
						//console.log('algo');
						
					}else if(parseInt(numseg[i].A) === seg2.xA && parseInt(numseg[i].B) == seg2.xB)
					{
						continue;
					}
					else
					{	
							//comparar con el segmento 1						
							xa1 = node[seg1.xA].cx;
							ya1 = node[seg1.xA].cy;			
							xb2 = node[seg1.xB].cx;
							yb2 = node[seg1.xB].cy;
							
							xa3 = node[numseg[i].A].cx;
							ya3 = node[numseg[i].A].cy;			
							xb4 = node[numseg[i].B].cx;
							yb4 = node[numseg[i].B].cy;
							
							d = distance(xa1,ya1,xb2,yb2,xa3,ya3,xb4,yb4);												
							
							
							if(d != -1)
							{
								//((x3-x4)*(x1*y2-y1*x2)-(x1-x2)*(x3*y4-y3*x4))/d;
								dx = ((xa3-xb4)*(xa1*yb2-ya1*xb2)-(xa1-xb2)*(xa3*yb4-ya3*xb4))/d;
								//((y3-y4)*(x1*y2-y1*x2)-(y1-y2)*(x3*y4-y3*x4))/d;
								dy = ((ya3-yb4)*(xa1*yb2-ya1*xb2)-(ya1-yb2)*(xa3*yb4-ya3*xb4))/d; 
																																											
								var resultado = ResultadoSegmento(xa1,ya1,xb2,yb2,xa3,ya3,xb4,yb4 , dx , dy);
								
								//si es diferente de -1 hay una interseccion
								if(resultado != -1)
								{									
										if(xa1 != xb4 && ya1 != yb4)
										{											
											resseg.push
											(
												{
													x1:xa1,												
													y1:ya1 , 
													x2:xb2 , 
													y2:yb2
												}
											);
										}																							
								}				
							}							
							
							//comparar con el segmento 2							
							xa1 = node[seg2.xA].cx;
							ya1 = node[seg2.xA].cy;			
							xb2 = node[seg2.xB].cx;
							yb2 = node[seg2.xB].cy;
							
							d = distance(xa1,ya1,xb2,yb2,xa3,ya3,xb4,yb4);												
							
							if(d != -1)
							{
								//((x3-x4)*(x1*y2-y1*x2)-(x1-x2)*(x3*y4-y3*x4))/d;
								dx = ((xa3-xb4)*(xa1*yb2-ya1*xb2)-(xa1-xb2)*(xa3*yb4-ya3*xb4))/d;
								//((y3-y4)*(x1*y2-y1*x2)-(y1-y2)*(x3*y4-y3*x4))/d;
								dy = ((ya3-yb4)*(xa1*yb2-ya1*xb2)-(ya1-yb2)*(xa3*yb4-ya3*xb4))/d; 
																																											
								var resultado = ResultadoSegmento(xa1,ya1,xb2,yb2,xa3,ya3,xb4,yb4 , dx , dy);
								
								//si es diferente de -1 hay una interseccion
								if(resultado != -1)
								{
										if(xa1 != xb4 && ya1 != yb4)
										{											
											resseg.push
											(
												{
													x1:xa1,												
													y1:ya1 , 
													x2:xb2 , 
													y2:yb2
												}
											);
										}																							
								}				
							}							
						
						if(resseg.length > 1)
						{
							return resseg;
						}						
					}
				}				
			}						
		}
			
			return -1;
	};	
	
	function distance(x1,y1, x2,y2, x3,y3, x4,y4)
	{
		// (x1-x2)*(y3-y4) - (y1-y2)*(x3-x4)
		var d = ((x1-x2)*(y3-y4)) - ((y1-y2)*(x3-x4));
		
		if (d == 0)
		{
		 return -1;
		} 			 
		else
		{
		 return d; 
		} 	
	}
	
	function ResultadoSegmento(x1,y1,x2,y2,x3,y3,x4,y4 , x , y)
	{
		//valida que los segmentos no sean verticales
		if (y < Math.min(y1,y2) || y > Math.max(y1,y2)) return -1;
		if (y < Math.min(y3,y4) || y > Math.max(y3,y4)) return -1;
		
		//valida que los segmentos no sean paralelos
		if (x < Math.min(x1,x2) || x > Math.max(x1,x2)) return -1;
		if (x < Math.min(x3,x4) || x > Math.max(x3,x4)) return -1;
		
		return 2;
	}
};

console.log('=============== >  Segment.js ');

/**
 * Model processing the draw canvas
 * 
 * Author Gerardo Irvin Campos yah
 */
function RegionEditor_Mvc(canvas, canvaso, aladin){

	this.node = [];	
	this.canvas = canvas[0];
	this.canvaso = canvaso[0];
	this.context = this.canvas.getContext('2d');
	this.contexto = this.canvaso.getContext('2d');
	//this.aladin parameters:
	this.aladin = aladin;	
	this.overlay = null;
	this.skyPositions = null;

}

RegionEditor_Mvc.prototype = {

		DrawNode: function (data){
			for(var i in data)
			{
				this.context.beginPath();
				this.context.arc(data[i].cx, data[i].cy, data[i].r, 0, Math.PI * 2,true);     	      
				this.context.fillStyle = "blue";
				this.context.fill();
				this.context.stroke();	 
				this.context.closePath();	  
			} 	     
		},

		//Drawn Line
		DrawnLine: function (startingNode,x,y,result) {
			if(result != null)
			{					
				this.context.beginPath();
				this.context.lineCap="round";

				for(i in this.node)
				{
					if(this.node[result.N] == i)
						this.context.moveTo(this.node[result.N].cx,this.node[result.N].cy);

					this.context.lineTo(this.node[i].cx,this.node[i].cy);				
				}					

				this.context.closePath(); 
				this.context.strokeStyle = 'lime';
				// this.context.lineWidth = 3;
				this.context.stroke();	
			}
			else
			{
				this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);		
				this.context.beginPath();
				this.context.lineCap="round";
				this.context.moveTo(this.node[startingNode].cx,this.node[startingNode].cy);		
				this.context.lineTo(x,y);
				this.context.closePath(); 
				this.context.strokeStyle = 'lime';
				//this.context.lineWidth = 3;
				this.context.stroke();
			}
		},

		//this.Redrawn line and this.node
		Redrawn : function (result)
		{				
			this.CanvasUpdate();
			for(var i in this.node)
			{
				this.context.beginPath();
				this.context.arc(this.node[i].cx, this.node[i].cy, this.node[i].r, 0, Math.PI * 2,true);     	      
				this.context.fillStyle = "red";
				this.context.fill();
				this.context.stroke();	 
				this.context.closePath();	        	    
			} 		

			this.DrawnLine(0,0,0,result);
		},	

		//Clean the this.canvas
		CanvasUpdate : function ()
		{
			this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
			this.contexto.clearRect(0, 0, this.canvas.width, this.canvas.height);
			this.contexto.drawImage(this.canvas, 0, 0);
		},

		//Convert a Array to Object
		ArrayToObject: function (data)
		{
			NodeTemp = [];
			for(i in data)
			{
				NodeTemp.push
				(
						{
							cx: data[i][0] ,
							cy: data[i][1],
							r:5
						}
				);
			}

			this.node=[];
			this.node = NodeTemp;
		},

		//Fuction pour obtenir le hautor du polygon
		GetHeight: function (array)
		{		
			var Ramax = null, Ramin = null;
			var finaltemp;
			var largeur;

			for(i in array)
			{
				temp = array[i][0];        	

				if(Ramax == null)
				{
					Ramax = temp;
				}
				else if(temp >= Ramax)
				{
					Ramax = temp;
				}

				if(Ramin == null)
				{
					Ramin = temp;
				}
				else if(temp <= Ramin )
				{
					Ramin = temp;
				}
			}

			largeur = (Ramax -Ramin);

			if(largeur > 180)
			{
				largeur = 360 - largeur;
			}

			return { ramax: Ramax, ramin: Ramin , largeur: largeur  };
		},

		//function pour obtenir le numero de segment et construir un segment
		NumeroSegmen : function ()
		{	
			var TotalNodes = this.node.length;		
			var segmentoini, segmentofin;	
			var total = [];

			for(var j=0; j<this.node.length; j++)
			{
				if(segmentoini == undefined)
					segmentoini = j;
				else if(segmentofin == undefined){
					segmentofin = j; 
				}

				if(segmentoini != undefined && segmentofin != undefined)
				{
					total.push
					({
						A: segmentoini,
						B: segmentofin
					});

					segmentoini = segmentofin;
					segmentofin = undefined;
				}
			}

			total.push
			({
				A: (this.node.length  - 1),
				B: 0
			});

			//console.log('total: ' + total.length);
			return total;
		},

		//function pour obtenir le hauteur de un polygone
		GetWidth: function (array)
		{		
			var Decmax = null, Decmin = null;	
			var temp;
			var width;

			for(i in array)
			{
				temp = (array[i][1]);        	

				if(Decmax == null)
				{
					Decmax = temp;
				}
				else if(temp >= Decmax)
				{
					Decmax = temp;
				}

				if(Decmin == null)
				{
					Decmin = temp;
				}
				else if(temp <= Decmin )
				{
					Decmin = temp;
				}
			}

			width = (Decmax - Decmin);

			if(width > 180)
			{
				width = 360 - width;
				//console.log('width 360');
			}

			return { decmax: Decmax, decmin: Decmin , width: width  };
		},

		//function para crear una grafica en el this.canvas
		DrawGrafic: function (canvas1)
		{
			var canvasgraf =  canvas1;
			var ancho = canvasgraf.width;
			var alto = canvasgraf.height;

			var contextGrafic = canvasgraf.getContext('2d');
			var contador = 20;
			var contador2 = 20;

			//console.log("ancho: " + ancho);
			//console.log("alto: " + alto);

			for(var i =0; i < alto ; i++)
			{

				this.contextGrafic.beginPath();

				if(i === 0)
				{
					this.contextGrafic.moveTo( i + 20 , 10);
					this.contextGrafic.lineTo( i + 20, alto);
					this.contextGrafic.fillStyle="black";
					this.contextGrafic.font = "bold 8px sans-serif";
					this.contextGrafic.fillText("0",i + 15 , 20);
				}
				else 
				{
					this.contextGrafic.moveTo( i + contador , 20);
					this.contextGrafic.lineTo( i + contador , alto);
					this.contextGrafic.fillStyle="black";
					this.contextGrafic.font = "bold 8px sans-serif";
					this.contextGrafic.fillText(i,(i+contador)-3 , 20);
				}

				this.contextGrafic.closePath(); 
				this.contextGrafic.strokeStyle = 'yellow';
				//this.context.lineWidth = 3;
				this.contextGrafic.stroke();	

				contador = parseInt( contador + 20);

			}

			for(var i =0; i < ancho ; i++)
			{

				this.contextGrafic.beginPath();
				this.contextGrafic.lineCap="round";

				if(i === 0)
				{
					this.contextGrafic.moveTo( 12 , i + 20 );
					this.contextGrafic.lineTo( ancho , i + 20);	
				}
				else 
				{
					this.contextGrafic.moveTo( 12  , 0 + contador2);
					this.contextGrafic.lineTo( ancho , 0 + contador2);
					this.contextGrafic.font = "bold 8px sans-serif";		     
					this.contextGrafic.fillStyle="black";
					this.contextGrafic.fillText(i, 3, (0+ contador2)+3);
				}

				this.contextGrafic.closePath(); 
				this.contextGrafic.strokeStyle = 'brown';
				//this.context.lineWidth = 3;
				this.contextGrafic.stroke();	
				contador2 = parseInt( contador2 + 20);	    	       
			}  
		},

		isEmpty: function()
		{
			if(this.node.length == 0)
				return true;		
			else
				return false;
		},

		//function que permet de ajouter this.nodes
		addNode: function(x, y,startingNode,polygonestatus)
		{					
			if(polygonestatus)
			{
				var newNode = {};
				var lastnode = {};
				var position = parseInt(startingNode[0].position);

				newNode.cx = startingNode[0].cx;
				newNode.cy = startingNode[0].cy;
				newNode.r = startingNode[0].r;

				if(this.node.length === position)
				{				
					lastnode.cx = this.node[(this.node.length -1)].cx;
					lastnode.cy = this.node[(this.node.length -1)].cy;
					lastnode.r = 5;

					//agregar el nodo
					this.node.splice((this.node.length -1), 1 , lastnode,newNode);				
				}
				else
				{
					lastnode.cx = this.node[startingNode[0].position].cx;
					lastnode.cy = this.node[startingNode[0].position].cy;
					lastnode.r = 5;

					//agregar el nodo
					this.node.splice(startingNode[0].position, 1 ,newNode, lastnode);
				}														
				this.Redrawn(0);
			}
			else
			{
				var flag = typeof(startingNode);
				if(flag != "object")
				{
					if(startingNode == 0 && this.node.length > 1)
					{		
						this.node.unshift
						(
								{
									cx: x,
									cy: y,
									r: 5,	                            
								}
						);
					}
					else
					{
						this.node.push
						(
								{
									cx: x,
									cy: y,
									r: 5            
								}
						);
					}
					this.DrawNode(this.node);
				}	
				else
				{

					if(startingNode != undefined /*&& startingNode.B != undefined*/)
					{					
						var addnode ={};
						var preview ={};					

						preview.cx = startingNode.segmento.xA;
						preview.cy = startingNode.segmento.yA;
						preview.r = 5;

						addnode.cx = x;
						addnode.cy = y;
						addnode.r = 5;

						this.node.splice(startingNode.segmento.segmento, 1 , preview , addnode);
						var renode =  this.node;
						this.Redrawn(0);

					}
				}			          		         
			}

			//console.log('this.node add: ' + this.node.length);        
		},

		//function que permet obtener le numero de this.node
		getNode: function(x,y)
		{
			var dx=0 , dy=0;
			var result = 0;

			for(var i in this.node)
			{	             
				dx = x - this.node[i].cx;
				dy = y - this.node[i].cy;  
				//var result =Math.sqrt(dx * dx + dy * dy);
				var result = dx * dx + dy * dy;

				if(result <= 25)
				{	    
					//console.log('i: ' + i);
					return i;	
				}
			}
			return -1;
		},

		//function pour obtenir les deux this.nodes qui forme un segment
		getSegment: function(clickedNode)
		{		
			var pointA=0 ,pointB=0;

			if(clickedNode == 0)
			{		
				//console.log('nodo 0');
				pointA = (parseInt(clickedNode) +1);
				pointB = (this.node.length -1);
			}
			else if(clickedNode == (this.node.length -1))
			{			
				//console.log('nodo final:' + (this.node.length -1));
				pointA = parseInt((this.node.length -1) -1);
				pointB = 0;			
			}
			else if(clickedNode != 0 && clickedNode != (this.node.length -1))
			{	
				//console.log('otro this.node');
				pointA = (parseInt(clickedNode)+1);
				pointB = (parseInt(clickedNode)-1);			
			}
			return {A :pointA, B:pointB, N:clickedNode};
		},

		//function pour effacer le this.canvas
		canvasUpdate: function()
		{		
			this.contexto.drawImage(this.canvas, 0, 0);
			this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);		
		},

		//function pour diseigner les lignes
		drawHashline: function(startingNode,x,y)
		{						
			this.DrawnLine(startingNode,x,y);	   	   					
		},	

		//function pour effacer un ligne
		CleanLine: function()
		{	
			//this.contexto.clearRect(0, 0, this.canvas.width, this.canvas.height);
			this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
		},

		//function pour savoire si un this.node es un extemite
		isExtremity: function(clickedNode)
		{
			if(clickedNode == 0 || clickedNode == (this.node.length -1))
			{		
				return true;								
			}				
			return false;

		},

		//function que permet de fermer un polygon
		closePolygone: function(clickedNode , startingNode)
		{		
			if(clickedNode == startingNode)
			{
				return false;	
			}
			else if(clickedNode == 0 && startingNode == (this.node.length -1))
			{		
				for(var i in this.node)
				{
					this.context.beginPath();
					this.context.arc(this.node[i].cx, this.node[i].cy, this.node[i].r, 0, Math.PI * 2,true);     	      
					this.context.fillStyle = "red";
					this.context.fill();
					this.context.stroke();	 
					this.context.closePath();	  		        
				}  
				return true;
			}
			else if(clickedNode == (this.node.length -1) && startingNode == 0 )
			{			
				for(var i in this.node)
				{
					this.context.beginPath();
					this.context.arc(this.node[i].cx, this.node[i].cy, this.node[i].r, 0, Math.PI * 2,true);     	      
					this.context.fillStyle = "red";
					this.context.fill();
					this.context.stroke();	 
					this.context.closePath();	  		        
				} 
				return true;
			}			
			return false;
		},

		//function pour bouger un this.node et ses deux segments de le poligone
		Drag: function(clickedNode, x,y,result)
		{
			var segmentfirst;
			var segmentlast;
			var flag;
			var resultado = [];								

			//set new values
			this.node[clickedNode].cx = x;
			this.node[clickedNode].cy = y;	

			this.node[result.N].cx = x;
			this.node[result.N].cy = y;					

			this.Redrawn(result);		
		},

		//function pour garder les valeur de alafin lite et les convertir en valeurs de this.canvas("pixel")
		almacenar: function()
		{			
			//console.log('mesage this.almacenar');
			//console.log('this.skyPositions: ' + this.skyPositions);
			if(this.skyPositions != null)
			{
				console.log("ALSM " + this.skyPositions);


				//console.log('this.skyPositions' + this.skyPositions);
				//console.log('this.node' + this.node);					
				this.node = [];
				this.skyPositions.pop();

				for (var k=0; k<this.skyPositions.length; k++) 
				{
					this.node.push(this.aladin.world2pix
							(
									this.skyPositions[k][0], 
									this.skyPositions[k][1]								
							));								
				}	

				this.ArrayToObject(this.node);

				//console.log('this.node: ' + typeof(this.node));
				/*for(i in this.node)
				{	
					console.log('i : ' + i);
					console.log('this.node x: ' + this.node[i].cx);
					console.log('this.node y: ' + this.node[i].cy);				
				}*/

				this.Redrawn(this.node);	
			}

		},		

		//function pour effacer le poligone de this.aladin lite quand passe a mode edition
		DeleteOverlay :  function()
		{
			if (this.overlay != null) 
			{			 	      
				//console.log('this.skyPositions: ' + this.skyPositions);
				//console.log('A: ' + typeof(A));
				this.overlay.addFootprints(A.polygon(this.skyPositions));
				this.overlay.removeAll();
				this.overlay.overlays = [];
				//console.log('this.overlay' + this.overlay);			           
			}	        	 
		},

		//function pour obtenir les valeurs de le polygon et creer le polygon en adalin lite
		recuperar: function()
		{
			/*
			 * When the position are set from outside, the node remains empty while there is edition action.
			 *  So if the user want to get back the polygoene without editing it, we have to cancel this method
			 */
			if( this.node && this.node.length == 0 && this.skyPositions && this.skyPositions.length > 0 ) {
				return ;
			}
			//console.log('this.node1: ' + this.node.length);

			//console.log('this.node.length: ' + this.node.length);
			this.skyPositions = [];		 
			for (var k=0; k<this.node.length; k++) {
				//this.skyPositions.push(this.aladin.pix2world(this.node[k][0], this.node[k][1]));
				this.skyPositions.push(this.aladin.pix2world(this.node[k].cx, this.node[k].cy));
			};
			//finalthis.node
			if (this.overlay==null) {
				this.overlay = A.graphicOverlay({color: 'red'});
				this.aladin.addOverlay(this.overlay);
			}
			this.overlay.removeAll();	       
			this.overlay.addFootprints(A.polygon(this.skyPositions));
		},

		//function pour obtenir les valeurs de le polygon et creer le polygon en adalin lite
		setPolygon: function(points)
		{
			this.skyPositions = [];		 
			for( var k=0 ; k<points.length ; k++){
				this.skyPositions.push(points[k]);			
			}
			if (this.overlay==null) {
				this.overlay = A.graphicOverlay({color: 'red'});
				this.aladin.addOverlay(this.overlay);
			}
			this.overlay.removeAll();	       
			this.overlay.addFootprints(A.polygon(this.skyPositions));
			this.PolygonCenter();
		},

		//function pour effacer le poligone de this.canvas
		CleanPoligon: function()
		{
			this.CanvasUpdate();
			this.node = [];
			this.skyPositions= [];
			//console.log('this.node delete: ' + this.node.length);		
		},

		//trouver le polygon en adalin lite si on se trouve en otre part du universe
		PolygonCenter: function()
		{		
			var Height = this.GetHeight(this.skyPositions);		
			var width = this.GetWidth(this.skyPositions);
			if( Height.largeur == 0 || width.largeur == 0 ) {
				return;
			}
			var center = {};
			center.ra = ((Height.ramax +  Height.ramin)/2);
			center.dec =  ((width.decmax + width.decmin)/2);
			this.aladin.gotoPosition(center.ra, center.dec);
			this.aladin.setZoom( (width.width + Height.largeur) );
		},

		//effacer un this.node de le polygone si se trouve sûr autre this.node
		RemoveNode: function(nodevalue,status)
		{
			var index = this.node[nodevalue];

			if(this.node.length >= 4)
			{			
				this.node.splice(nodevalue,1);
				if(status)
				{
					this.DrawNode(this.node);
				}else
				{
					this.Redrawn(0);
				}

			}
		},

		//function pour obtenir le this.node initial et final du polygon
		GetXYNode: function(x,y)
		{
			var nodes={};        

			for(var i in this.node)
			{	         
				//console.log('this.nodenum:  ' + i);
				//console.log('cx: ' + this.node[i].cx);
				//console.log('cy: ' + this.node[i].cy);
				dx = x - this.node[i].cx;
				dy = y - this.node[i].cy;  
				//var result =Math.sqrt(dx * dx + dy * dy);
				var result = dx * dx + dy * dy;

				if(result <= 25)
				{	                	
					if(nodes.a == undefined)
					{
						nodes.a = i;
					}
					else 
					{
						nodes.b = i;
					}            		            		
				}                      
			}

			return nodes;
		},

		//metodo que debuelve el numero de nodos del poligono
		GetNodelength: function()
		{
			return this.node;
		},

		//crear la grafica
		createGrafic: function(parametre)
		{
			this.DrawGrafic(parametre);
		},

		//indicar cuando serrar poligono
		cuadradoIndicador: function(x,y)
		{	
			this.context.beginPath();
			this.context.fillRect(x,y,10,10);     	      
			this.context.fillStyle = "red";
			this.context.fill();
			this.context.stroke();	 
			this.context.closePath();
		},

		stokeNode: function(nodeposition)
		{
			if(nodeposition != undefined) 
				var stocknode = [];
				stocknode.push
				({
					position: nodeposition,
					cx:this.node[nodeposition].cx,
					cy:this.node[nodeposition].cy,
					r:5
				});

				return stocknode;
			
		},
		getSkyPositions: function() {
			return this.skyPositions;
		}
}
console.log('=============== >  RegionEditor_m.js ');

/**
 * Manager of the view of the region editor
 * 
 * Author Gerardo Irvin Campos yah
 */
function 
RegionEditor_mVc(parentDivId, handler, points){
	this.parentDivId = parentDivId;
	this.drawCanvas = null; // canvas where the polygon is drawn
	this.drawContext = null;
	this.lineCanvas = null; // canvas where the moving lines are drawn
	this.lineContext = null;
	this.controller = null;
	this.points = points; // Initial values
	this.clientHandler = (handler == null) ? function(){Modalinfo.info("No client handler registered");}: handler;
} 

RegionEditor_mVc.prototype = {
		init: function (data){
			// création instance d'Aladin lite
			$('#' + this.parentDivId).append('<div id="' + this.parentDivId + '_aladin" style="width: 390px; height: 320px;"></div><div id="' + this.parentDivId + '_button"></div>');
			this.aladin = $.aladin('#' + this.parentDivId + '_aladin', {showControl: true, fov: 0.5, target: "orion", cooFrame: "J2000", survey: "P/DSS2/color", showFullscreenControl: false, showFrame: false, showGotoControl: false});
			this.aladin.setImageSurvey("P/XMM/PN/color");
			this.parentDiv = this.aladin.getParentDiv();
			$('#' + this.parentDivId).css("position", "relative");

			// création du canvas pour éditeur régions
			/*
			 * Be cautious: the canvas context must be taken before the canvas is appended to the parent div, otherwise the geometry is wrong. 
			 */
			this.lineCanvas = $("<canvas id='RegionCanvasTemp' class='editor-canvas'></canvas>");
			this.lineCanvas[0].width = this.parentDiv.width();
			this.lineCanvas[0].height = this.parentDiv.height();
			this.lineContext = this.lineCanvas[0].getContext('2d');	        
			this.parentDiv.append(this.lineCanvas);
			this.lineCanvas.css('z-index', '100');
			this.lineCanvas.css('position', 'absolute');
			this.lineCanvas.hide(); 

			/*
			 * Canvas pour les traces temporaires
			 */
			this.drawCanvas = $("<canvas id='RegionCanvas' class='editor-canvas'></canvas>");
			this.drawCanvas[0].width = this.parentDiv.width();
			this.drawCanvas[0].height = this.parentDiv.height();
			this.drawContext = this.drawCanvas[0].getContext('2d');
			this.parentDiv.append(this.drawCanvas);
			this.drawCanvas.css('z-index', '100');
			this.drawCanvas.css('position', 'absolute');
			this.drawCanvas.hide(); 


			this.controller = new RegionEditor_mvC({ "points": this.points, "handler": this.clientHandler, "canvas": this.drawCanvas, "canvaso": this.lineCanvas, "aladin": this.aladin});
			/*
			 * The controller function is wrapped in a function in order to make it working in the context of the controller object
			 * and not of he HTML widget
			 */
			var that = this;
			this.drawCanvas[0].addEventListener('mousedown', function(event) {that.controller.mouseDown(event);}, false);
			this.drawCanvas[0].addEventListener('mousemove',  function(event) {that.controller.mouseMove(event);}, false);
			this.drawCanvas[0].addEventListener('mouseup', function(event) { that.controller.mouseUp(event);}, false);

			/*----crear botones con jquery----*/
			var divButtons = $("<div id='RegionButtons' style=' width:"+ this.parentDiv.width() +'px' +" ';' '><div/>").appendTo("#" + this.parentDivId + "_button");        
			divButtons.css('background', 'gray');//'height:' "+ 200 +'px' +"';'
			divButtons.css('height', '70px');

			this.browseBtn = $("<input type='button' id='edit' value='Browse' />");
			divButtons.append(this.browseBtn);
			this.browseBtn.css('margin-top','10px');
			this.browseBtn.css('margin-left','5px');
			this.browseBtn.attr('disabled', 'disabled');
			this.browseBtn.click(function() {        	 
				that.controller.recuperar();   
				that.setBrowseMode();
			});

			this.editBtn = $("<input type='button' id='edit' value='Edit' />");
			divButtons.append(this.editBtn);
			this.editBtn.css('margin-top','10px');
			this.editBtn.css('margin-left','5px');
			var that = this;
			this.editBtn.click(function() {        	 
				that.setEditMode();
				that.controller.DeleteOverlay()
				that.lineContext.clearRect(0, 0, that.lineCanvas[0].width, that.lineCanvas[0].height);            
				that.drawContext.clearRect(0, 0, that.drawCanvas[0].width, that.drawCanvas[0].height);
				that.controller.almacenar();	       
			});


			this.centerBtn = $("<input type='button' id='edit' value='Center' />");
			divButtons.append(this.centerBtn);
			this.centerBtn.css('margin-top','10px');
			this.centerBtn.css('margin-left','5px');
			this.centerBtn.click(function() {        	 
				that.controller.PolygonCenter();
			});

			this.effacerBtn = $("<input type='button' id='edit' value='clear' />");
			divButtons.append(this.effacerBtn);
			this.effacerBtn.css('margin-top','10px');
			this.effacerBtn.css('margin-left','5px');
			this.effacerBtn.click(function() {        	 
				that.controller.CleanPoligon();
			});
			this.setBrowseMode();

			var buttonSet = $("&nbsp;&nbsp;<input type='button' id='edit' value='Accept' />");
			divButtons.append(buttonSet);
			buttonSet.css('margin-top','10px');
			buttonSet.css('margin-left','5px');
			buttonSet.css('font-weight',' bold');
			buttonSet.click(function() {
				that.controller.recuperar();  
				that.setBrowseMode();
				that.controller.invokeHandler(true);
			});
			this.posField = $("<br><input type='text' id='position' size=24 />");
			divButtons.append(this.posField);
			this.posField.css('margin-top','10px');
			this.posField.css('margin-left','5px');
			this.posField.keyup(function(e){
				if(e.keyCode == 13){
					var pos = $(this).val().replace(/:/g , " ");
					that.posField.val(pos);
					that.aladin.gotoObject(pos);
					$(this).val(pos);
					that.aladin.gotoObject(pos);
				}
			});

		},
		/**
		 * Initalize the darw with the default parameter. If points contains a region, it is drawn, 
		 * if it just contain a position, AladinLite is centered on that position
		 * @param points  object denoting the initial value of the polygone : {type: ... value:} type is format of the 
		 * value (saadaql or array) and value is the data string wich will be parsed
		 */
		setInitialValue: function (points){
			/*
			 * Set the region passed by the client if it exists
			 */
			this.points = points;
			this.controller.CleanPoligon();
			if( this.points ){
				var pts = [];
				/*
				 * Extract region or position from SaadaQL statement
				 */
				if( this.points.type == "saadaql") {
					var s = /"(.*)"/.exec(this.points.value);
					if( s.length != 2 ) {
						Modalinfo.error(this.points.value + " does not look like a SaadaQL statment");
						return;
					} else {
						if( this.points.value.startsWith("isInRegion")) {
							var ss = s[1].split(/[\s,;]/);
							for( var i=0 ; i<ss.length ; i++ ) {
								pts.push(parseFloat(ss[i]));
							}
						} else {
							var pos = s[1].replace(/:/g , " ");
							this.posField.val(pos);
							this.aladin.gotoObject(pos);
						}
					}
				} else if (this.points.type == "array") {
					pts = this.points.value;
				} else {
					Modalinfo.error("Polygone format " + points.type + " not understood");
					return;
				}
//				this.points = [84.24901652054093, -5.640882748140112,83.34451837951998, -6.103216341255678,83.60897420186223, -4.553808802262613, 84.24901652054093, -5.640882748140112];			
				this.setBrowseMode();
				this.controller.DeleteOverlay()
				this.controller.setPoligon(pts);
			}

		},
		setBrowseMode: function() {
			this.editBtn.removeAttr('disabled');
			this.browseBtn.attr('disabled', 'disabled');   
			this.effacerBtn.attr('disabled', 'disabled');                      
			this.lineCanvas.hide();
			this.drawCanvas.hide();
		},
		setEditMode: function() {
			this.browseBtn.removeAttr('disabled');
			this.editBtn.attr('disabled', 'disabled');   
			this.effacerBtn.removeAttr('disabled');                
			this.lineCanvas.show();
			this.drawCanvas.show();
		},

}


console.log('=============== >  RegionEditor_v.js ');

/**
 * Controller handling the user actions in connection with the model 
 * 
 *  params = {canvas,canvaso, aladin}
 * 
 * Author Gerardo Irvin Campos yah
 */
function /**
 * @author michel
 *
 */
RegionEditor_mvC(params){
	//this.poligone =  new poligoneCreate(params.canvas, params.canvaso, params.aladin);
	this.poligone =  new RegionEditor_Mvc(params.canvas, params.canvaso, params.aladin);

	this.canvas = params.canvas; 	
	this.clientHandler = params.handler;
	this.startingNode= -1; 
	this.buttondown = false; 
	this.closed = false;	
	this.movestart = false;
	this.startdrag = false;
	this.drag = null;
	this.result = -1;
	this.stokeNode;
	var that = this;
}

RegionEditor_mvC.prototype = {
		getStatus: function() {
			 return "startingNode=" 
			        +this.startingNode + " buttondown=" 
			  		+ this.buttondown+ " closed=" 
			  		+ this.closed+ " movestart=" 
			  		+ this.movestart + " startdrag=" 
			  		+ this.startdrag + " drag=" 
			  		+ this.drag  + " result=" 
			  		+ this.result + " stokeNode=" 
			  		+ this.stokeNode
			  		;
		},
		/**
		 * TODO to be implemented
		 */
		checkPolygon : function(points) {
			return true;
		},
		/**
		 * 
		 */
		mouseDown : function(event) {
			var clickedNode = -1;
			var clickedSegment = -1;
			var x = parseInt(event.pageX) - parseInt( this.canvas.offset().left).toFixed(1);
			var y = parseInt(event.pageY) - parseInt( this.canvas.offset().top).toFixed(1);
					
			//pregunta si el pologono esta vacio
			if( this.poligone.isEmpty()) 
			{
				this.poligone.addNode(x,y);			 
			}
			//obtener segmento
			
			//comenzar el this.drag del nodo		
			else if(this.closed == true && (clickedNode = this.poligone.getNode(x,y)) != -1)
			{
				//console.log('start this.drag');
				//console.log('clickedNode: ' + clickedNode);
				this.result = this.poligone.getSegment(clickedNode);
				this.stokeNode = this.poligone.stokeNode(clickedNode);
				this.startdrag = true;		
				this.drag = clickedNode;
				this.startingNode = clickedNode;		
				this.canvas.css('cursor','move');
			}
			//pregunta si el espacio presionado es un nodo 
			else if((clickedNode = this.poligone.getNode(x,y)) != -1 )
			{
				//pregunta si es una extremidad
				if(this.poligone.isExtremity(clickedNode) /*poligono abierto*/) 
				{			
					//pregunta estas abierto
					if(this.closed == true)
					{
						this.startingNode = -1;
						this.buttondown = false;	
					}
					else
					{
						this.startingNode = clickedNode;
						this.buttondown = true;					
						this.closed = false;
					}
				}							
			} 		
			
			//saber si estoy sobre un segmento
			if(this.closed && clickedNode == -1)
			{						
				var node = this.poligone.GetNodelength();	
						
				var Segmentos = new Segment(node);	
				var option = Segmentos.IsCursorOn(x,y);
				
				if(option != undefined)
				{
					if(option.flag == "vertical")
					{
						//console.log("option: " + option.flag);
						this.poligone.addNode(x, y, option);
					}
					else if(option.flag == "horizontal")
					{
						//console.log("option: " + option.flag);
						this.poligone.addNode(x, y, option);
					}
					else if(option.flag == "distancia")
					{
						//console.log("option: " + option.flag);
						this.poligone.addNode(x, y, option);
					}
				}						
			
			}
			
		},
		/**
		 * 
		 */
		mouseMove : function(event) {
			var x = parseInt(event.pageX) - parseInt( this.canvas.offset().left).toFixed(1);
			var y = parseInt(event.pageY) - parseInt( this.canvas.offset().top).toFixed(1);
			//console.log("mouse move " + this.getStatus());
			//pregunta si el nodo fue presionado y si es un nodo
			if(this.buttondown == true  && this.startingNode != -1 )
			{
				//console.log ('this.drag');
				//console.log ('this.startingNode' + this.startingNode);
				this.movestart = true;
				this.poligone.drawHashline(this.startingNode,x,y,this.result);		
			}		
			else if(this.startdrag)
			{
				this.poligone.Drag(this.drag, x, y , this.result);
				
				//console.log('this.startdrag move');		
			}
			
//			var h2x = document.getElementById("idcoor");
//			h2x.innerHTML = 'X coords: '+x+', Y coords: '+y;
		},
		
		mouseUp: function(event) {
			var clickedNode = -1;
			var x = parseInt(event.pageX) - parseInt( this.canvas.offset().left).toFixed(1);
			var y = parseInt(event.pageY) - parseInt( this.canvas.offset().top).toFixed(1);		
		//pregunta nodo es presionado y es si es un nodo
			if(this.buttondown == true && (clickedNode = this.poligone.getNode(x,y)) != -1 )
			{		
				//pregunta si es un extremo
				if( this.poligone.isExtremity(clickedNode) == false) 
				{				
					this.poligone.CleanLine();				
					this.buttondown = false;
				}	
				
				//console.log('clickedNode: ' + clickedNode + ' this.startingNode: ' +  this.startingNode);
				if(this.poligone.closePolygone(clickedNode , this.startingNode) == true)
				{
					//console.log('this.closed polygon');					
					this.buttondown = false;	
					this.closed = true;
					this.invokeHandler(false);
				
					//console.log('clickedNode: ' + clickedNode + ' this.startingNode: ' +  this.startingNode);							
				}
			} 
			
			if(this.closed == true && (finalnode = this.poligone.GetXYNode(x, y) ) != null)			
			{
				if(finalnode.a != undefined && finalnode.b != undefined)
				{
					//console.log('finalnode a: ' + finalnode.a + ' finalnode b: ' + finalnode.b);
					
					if(this.startingNode ==  finalnode.a)
						this.poligone.RemoveNode(finalnode.a,false);
					else if(this.startingNode ==  finalnode.b)
						this.poligone.RemoveNode(finalnode.b,false);
				}			
			}
					
			if(this.buttondown == true && this.movestart == true)
			{		
				if( clickedNode == this.startingNode && (clickedNode = this.poligone.getNode(x,y) != -1) )
				//if((clickedNode = this.poligone.getNode(x,y)) != -1)
				{											
					this.buttondown = false;		
					this.movestart = false;	
					this.poligone.CleanLine();							
				}				
				else
				{						
						this.poligone.addNode(x,y,this.startingNode);
						this.buttondown = false;		
						this.movestart = false;	
						
						var nodos = this.poligone.GetNodelength();					
						var Segmentos = new Segment(nodos);	
						var temp;
						
						var inter = Segmentos.Itersection(this.startingNode,false);
						
						if(inter != -1 && inter != undefined)
						{			
							//poligono abierto = true
							if(this.startingNode != 0)
								this.poligone.RemoveNode(inter.nB,true);
							else
								this.poligone.RemoveNode(inter.nA,true);
							
							this.poligone.CleanLine();
						}												
				}			
				
			}
			else if(this.buttondown == true && this.movestart == false)
			{			
				this.buttondown = false;		
				this.movestart = false;	
			}
			
			if(this.startdrag == true)
			{
				//console.log('this.startdrag fin');
				this.startdrag = false;
				this.canvas.css('cursor','default');
				
				//stoke le numero de noeud appuyer
				//this.startingNode;			
				
				var nodos = this.poligone.GetNodelength();					
				var Segmentos = new Segment(nodos);	
				var inter = Segmentos.Itersection(this.startingNode,true);			
				if(inter != -1 && inter != undefined)
				{						
					this.poligone.RemoveNode(this.startingNode, false);
					this.poligone.addNode(x, y, this.stokeNode,true);
					//console.log(inter.length);
				}
			}
			this.poligone.canvasUpdate();
		},
		
		almacenar : function()
		{
			this.poligone.almacenar();
		},
		
		recuperar : function()
		{
			this.poligone.recuperar();
		},
		
		DeleteOverlay : function() {
			this.poligone.DeleteOverlay();
		},
		
		CleanPoligon : function(){
			this.poligone.CleanPoligon();
			this.closed = false;
		},
		
		PolygonCenter : function(){
			this.poligone.PolygonCenter();
		},
	
		CreateGrafic : function(canvas){
			this.poligone.createGrafic(this.canvas);
		},
		
		show : function() {
			console.log(this.poligone.getSkyPositions());
			alert(this.poligone.getSkyPositions());
		},
		/**
		 * Set the polygone with points. Points is a simple array. It must have at 
		 * least 6 values (3pts) and an even number of points
		 * @param points  [a,b,c,.....]
		 * @returns {Boolean} true if the polygone is OK
		 */
		setPoligon : function(points) {
			if( !points || (points.length%2) != 0 || points.length < 6 ) {
				return false;
			} else {
				var x = [];
				for(var i=0 ; i<(points.length/2) ; i++){
					x[x.length] = [points[2*i], points[(2*i)+1]];
				}
				this.poligone.setPolygon(x);
				this.closed = true;
				this.invokeHandler(false);
				return true;
			}
		},
		/**
		 * Call the client handler when the polygine is close or when the user click on accept
		 * The data passed to the user handler look like that:
		    {isReady: true,             // true if the polygone is closed
		    userAction: userAction,     // handler called after the user have clicked on Accept
		    region : {
		        format: "array2dim",    // The only one suported yet [[x, y]....]
		        points: this.poligone.skyPositions  // array with structire matching the format
		        size: {x: , y:} // regiosn size in deg
		        }
		 */
		invokeHandler : function(userAction){
			if( this.closed || ( this.poligone.node == undefined || this.poligone.node.length == 0) ){
				/*
				 * Compute the region size in degrees
				 */
				for( var p=0 ; p<this.poligone.skyPositions.length ; p++ ) {
					for( var q=(p+1) ; q<this.poligone.skyPositions.length ; q++ ) {
						var x = Math.abs(this.poligone.skyPositions[p][0] - this.poligone.skyPositions[q][0]);
						if( x > 180 )x = 360 - x; 
						var y = Math.abs(this.poligone.skyPositions[p][1] - this.poligone.skyPositions[q][1]);
						if( y > 180 )y = 360 - y; 
					}
				}
				this.clientHandler({isReady: true
					, userAction: userAction
					, region : {format: "array2dim"
						       , points: this.poligone.skyPositions
							   , size: {x: x, y: y}}});
			} else {
				Modalinfo.error("Polygon not closed");
			}
		}
}

console.log('=============== >  RegionEditor_c.js ');


/***********************************************************************************************
 * Javascript classes common to all Web application supported by LM
 * These classes  refer to some basic web resource
 * 
 * Content:
 * - Modalinfo.simbad : open a Simbad view in a modal dialog
 * - Modalinfo.uploadForm : open a Simbad dialog box handling file upload 
 * and all Query editor components
 * - DataLinkBrowser
 * Required external resources 
 * - basics.js
 * - jquery-ui
 * - jquery.alerts
 * - jquery.datatables
 * - jquery.simplemodal
 * - jquery.prints
 * - jquery.tooltip
 * - jquery.forms
 * 
 * Laurent Michel 20/12/2012
 */

var supportedUnits =  [
                       {id: 'none', text: "none"}

                       , {id: 'Power_erg/s', text: "erg/s"}
                       , {id: 'Power_W', text: "W"}

                       , {id: 'Flux_erg/s/cm2', text: "erg/s/cm2"}
                       , {id: 'Flux_Jy', text: "Jy"}
                       , {id: 'Flux_mJy', text: "mJy"}
                       , {id: 'Flux_mJy', text: "mJy"}

                       , {id: 'Angle_deg', text: "deg"}
                       , {id: 'Angle_arcmin', text: "arcmin"}
                       , {id: 'Angle_arcsec', text: "arcsec"}
                       , {id: 'Angle_h:m:s', text: "h:m:s"}

                       , {id: 'Velocity_m/s', text: "m/s"}
                       , {id: 'Velocity_km/s', text: "km/s"}
                       , {id: 'Velocity_km/h', text: "km/h"}
                       , {id: 'Velocity_mas/yr', text: "mas/yr"}

                       , {id: 'Length_kpc', text: "kpc"}
                       , {id: 'Length_pc', text: "pc"}
                       , {id: 'Length_AU', text: "AU"}
                       , {id: 'Length_km', text: "km"}
                       , {id: 'Length_m', text: "m"}
                       , {id: 'Length_cm', text: "cm"}
                       , {id: 'Length_mm', text: "mm"}
                       , {id: 'Length_um', text: "um"}
                       , {id: 'Length_nm', text: "nm"}
                       , {id: 'Length_Angstroem', text: "Angstroem"}

                       , {id: 'Energy_erg', text: "erg"}
                       , {id: 'Energy_eV', text: "eV"}
                       , {id: 'Energy_keV', text: "keV"}
                       , {id: 'Energy_MeV', text: "MeV"}
                       , {id: 'Energy_GeV', text: "GeV"}
                       , {id: 'Energy_TeV', text: "TeV"}
                       , {id: 'Energy_J', text: "J"}
                       , {id: 'Energy_ryd', text: "ryd"}

                       , {id: 'Frequency_Hz', text: "Hz"}
                       , {id: 'Frequency_KHz', text: "KHz"}
                       , {id: 'Frequency_MHz', text: "MHz"}
                       , {id: 'Frequency_GHz', text: "GHz"}
                       , {id: 'Frequency_THz', text: "THz"}

                       , {id: 'Time_y', text: "y"}
                       , {id: 'Time_d', text: "d"}
                       , {id: 'Time_h', text: "h"}
                       , {id: 'Time_mn', text: "mn"}
                       , {id: 'Time_sec', text: "sec"}
                       , {id: 'Time_msec', text: "msec"}
                       , {id: 'Time_nsec', text: "nsec"}
                       ];

/**
 * Object modeling a dataTreePath
 * 
 * Code de test sur JSFILLDE
	var dtp = new DataTreePath({signature:"a.b.c"});
	alert(JSON.stringify(dtp));
	dtp = new DataTreePath({nodekey:"node", schema: "schema", table: "table"});
	alert(JSON.stringify(dtp));
	dtp = new DataTreePath({nodekey:"node", schema: "schema", tableorg: "tableorg"});
	alert(JSON.stringify(dtp));
	dtp = new DataTreePath({nodekey:"node", schema: "schema", table: "table", tableorg: "tableorg"});
	alert(JSON.stringify(dtp));
	dtp = new DataTreePath({nodekey:"node", schema: "schema", table: "table", tableorg: "tableorg", jobid: "jobid"});
	alert(JSON.stringify(dtp));
 *
 * Used to make sure that dataTreePath given as parameters as more or less consistent 
 * @param params
 * @returns {DataTreePath}
 */
function DataTreePath(params){
	this.nodekey = "notset";
	this.schema = "notset";
	this.table = "notset";
	this.tableorg = "notset";
	this.tableorg = "notset";
	this.jobid = "";
	this.key = "notset";
	/*
	 * received a signature like nodekey.schema.table
	 */
	if( 'signature' in params ){
		var fields = params.signature.split(".");
		this.nodekey = fields[0];
		if( fields.length > 1 ) {
			this.schema = fields[1];
			if( fields.length > 2 ) {
				this.table = fields[2];
				this.tableorg = fields[2];
				if( fields.length > 3 ) {
					this.jobid = fields[3];
				}
			}
		}
		/*
		 * received fields in a JSON object
		 */	
	} else {
		if( 'jobid' in params ){
			this.jobid = params.jobid;
		}
		if( 'nodekey' in params ){
			this.nodekey = params.nodekey;
		}
		if( 'schema' in params ){
			this.schema = params.schema;
		}
		if( 'table' in params ){
			this.table = params.table;
		}
		if( 'tableorg' in params ){
			this.tableorg = params.tableorg;
		}
		if(  this.table == "notset" && this.tableorg != "notset")
			this.table = this.tableorg;
		else if(  this.table != "notset" && this.tableorg == "notset")
			this.tableorg = this.table;
	}
	this.key = this.nodekey + "." + this.schema + "." + this.tableorg + ((this.jobid)? ("." + this.jobid): "");
};

MetadataSource = function() {
	var getMetaTableUrl = null;
	var getJoinedTablesUrl = null;
	var getUserGoodieUrl = null;
	var isAjaxing = false;
	/*
	 * buffer = {  dataTreePath
	 *   hamap <== map of ah
	 *   targets:[{target_datatreepath, source columns, target column}...]
	 *
	 * cache = map<DataTreePath.key, buffer>
	 */
	var cache = new Object;
	/**
	 * private methods
	 */
	/*
	 * setAttMap: Normalize AHs and store them in the buffer
	 */
	var buildAttMap = function (data){
		var hamap  = new Array();
		if( data.attributes ) {
			for( var i=0 ; i<data.attributes.length ; i++ ){
				var ah = data.attributes[i];
				/*
				 * Make sure that all naming fields are populated
				 * if possible:
				 */
				if( !ah.nameattr && ah.column_name  ) {
					ah.nameattr = ah.column_name;
				} else if( ah.nameattr && !ah.column_name ) {
					ah.column_name = ah.nameattr ;
				}
				if( !ah.nameattr  && ah.nameorg ){
					ah.nameattr = ah.nameorg ;
					ah.column_name = ah.nameorg ;
				} else if (!ah.nameorg ) {
					ah.nameorg = ah.nameattr ;
				}
				hamap.push(ah);
			}
		} 
		return hamap;
	};
	/**
	 * public methods
	 */
	/**
	 * init(): params : {getMetaTable, getJoinedTables, getUserGoodie}
	 */
	var init = function (params) {
		if( 'getMetaTable' in params ){
			getMetaTableUrl = params.getMetaTable;
		}
		if( 'getJoinedTables' in params ){
			getJoinedTablesUrl = params. getJoinedTables;
		}
		if( 'getUserGoodie' in params ){
			getUserGoodieUrl = params.getUserGoodie;
		}
	};
	/**
	 * dataTreePath: instance of the classe DataTreePath
	 */
	var getTableAtt =  function(dataTreePath /* instance of DataTreePath */, handler){
		/*
		 * Query a new node
		 */
		key = JSON.stringify(dataTreePath) 
		if(  cache[key] == undefined ) {
			var buffer = {};
			buffer.dataTreePath = dataTreePath;		
			buffer.hamap        = new Array();
			buffer.targets      = new Array();
			if( getMetaTableUrl != null ) {
				isAjaxing = true;
				Out.info("Connect new node  " + key);

				var params = {jsessionid: this.sessionID, nodekey: buffer.dataTreePath.nodekey, schema:buffer.dataTreePath.schema, table:buffer.dataTreePath.tableorg};
				Processing.show("Get column description of table " +JSON.stringify(dataTreePath) );
				$.ajax({
					url: getMetaTableUrl,
					dataType: 'json',
					async: false,
					data: params,
					success:  function(data)  {
						Processing.hide();
						if( !Processing.jsonError(data)) {
							buffer.hamap = buildAttMap(data);
							isAjaxing = false;
						}
					}
				});
				//if( getJoinedTablesUrl != null ){
					Processing.show("Waiting on join keys " + getJoinedTablesUrl);
					$.ajax({
						url: getJoinedTablesUrl,
						dataType: 'json',
						async: false,
						data: params,
						success:  function(jdata)  {
							Processing.hide();
							if( jdata && !jdata.errormsg )  {
								var dt = jdata.targets;			
								for( var i=0 ; i<dt.length ; i++ ) {
									var t = dt[i];
									var st = t.target_table.split('.');
									var schema, table;
									if( st.length > 1) {
										schema =  st[0]; table = st[1];
									} else {										
										schema =  dataTreePath.schema; table = st[0];
									}
									var tdtp = new DataTreePath({nodekey: dataTreePath.nodekey, schema: schema, table: table});
									buffer.targets.push({target_datatreepath: tdtp, target_column: t.target_column, source_column: t.source_column});
								}
							} else {
								Out.info((jdata == null)? 'Empty data' : jdata.errormsg);
							}
							isAjaxing = false;
							//if( handler != null ) handler();
						}
					});
					cache[key] = buffer;
				//}
			} else {
				Out.info("No getMetaTableUrl provided" );
			}
			if( handler != null ) handler();
			/*
			 * Meta data are in the cache
			 */
		} else  {
			Out.info("get stored node  " + key + " length = " + cache[key].hamap.length);
			if( handler!= null ) handler(); else Out.info("No handler");
			return cache[key].hamap;
		}
	};
	var getTableAttASync =  function(dataTreePath /* instance of DataTreePath */, handler){
		/*
		 * Query a new node
		 */
		if( isAjaxing ) {
			setTimeout(function(){MetadataSource.getTableAtt(dataTreePath, handler);}, 100);
			return;
		} 
		//console.log("Looking for " + JSON.stringify(dataTreePath) + " " + isAjaxing);
		if( !cache || !cache[dataTreePath.key] ) {
			buffer = {};
			buffer.dataTreePath = dataTreePath;		
			buffer.hamap        = new Array();
			buffer.targets      = new Array();
			if( getMetaTableUrl != null ) {
				isAjaxing = true;
				Out.infoTrace("Connect new node  " + dataTreePath.nodekey);

				var params = {jsessionid: this.sessionID, nodekey: buffer.dataTreePath.nodekey, schema:buffer.dataTreePath.schema, table:buffer.dataTreePath.tableorg};
				//console.log("Looking for " + JSON.stringify(dataTreePath) + " " + isAjaxing);
				Processing.show("Get column description of table " +JSON.stringify(dataTreePath) );
				$.getJSON(getMetaTableUrl
						, params
						, function(data)  {
					Processing.hide();
					if( !Processing.jsonError(data)) {
						buffer.hamap = buildAttMap(data);
						if(getJoinedTablesUrl) {
							Processing.show("Waiting on join keys " + getJoinedTablesUrl);
							$.getJSON(getJoinedTablesUrl
									, params
									, function(jdata) {
								Processing.hide();
								if( jdata && !jdata.errormsg )  {
									var dt = jdata.targets;			

									for( var i=0 ; i<dt.length ; i++ ) {
										var t = dt[i];
										var st = t.target_table.split('.');
										var schema, table;
										if( st.length > 1) {
											schema =  st[0]; table = st[1];
										} else {										
											schema =  dataTreePath.schema; table = st[0];
										}
										var tdtp = new DataTreePath({nodekey: dataTreePath.nodekey, schema: schema, table: table});
										buffer.targets.push({target_datatreepath: tdtp, target_column: t.target_column, source_column: t.source_column});
									}
								} else {
									Out.info((jdata == null)? 'Empty data' : jdata.errormsg);
								}
								isAjaxing = false;
								cache[dataTreePath.nodekey] = buffer;
								if( handler != null ) handler();
							});
						} else {
							isAjaxing = false;
							cache[dataTreePath.nodekey] = buffer;
							if( handler != null ) {
								if( handler != null ) handler();
							}
						}
					}
				});
			} else {
				Out.info("No getMetaTableUrl provided" );
			}
			/*
			 * Meta data are in the cache
			 */
		} else if( cache[dataTreePath.nodekey] ) {
			Out.info("get stored node  " + dataTreePath.nodekey + " length = " + cache[dataTreePath.nodekey].hamap.length);
			if( handler!= null ) handler(); else Out.info("No handler");
			return cache[dataTreePath.nodekey].hamap;
		}
	};
	var test =  function(){
		MetadataSource.init({getMetaTable: "gettableatt", getJoinedTables: "gettablejoin", getUserGoodie: null});
		var dataTreePath = new DataTreePath({nodekey:'node', schema: 'schema', table: 'table', tableorg: 'schema.table'});
		MetadataSource.getTableAtt(
				dataTreePath
				, function() {
					console.log("ahMap "        + JSON.stringify(MetadataSource.ahMap(dataTreePath)));
					console.log("joinedTables " + JSON.stringify(MetadataSource.joinedTables(dataTreePath)));	
					//	alert("ahMap "        + JSON.stringify(MetadataSource.ahMap(dataTreePath)));
				});
		MetadataSource.getTableAtt(
				dataTreePath
				, function() {
					console.log("ahMap "        + JSON.stringify(MetadataSource.ahMap(dataTreePath)));
					console.log("joinedTables " + JSON.stringify(MetadataSource.joinedTables(dataTreePath)));	
					//	alert("ahMap "        + JSON.stringify(MetadataSource.ahMap(dataTreePath)));
				});
	};

	var getJoinedTables =  function(){};
	var getUserGoodie =  function(){};
	var setJobColumns =  function(){};

	/*
	 * exports
	 */
	var pblc = {};
	pblc.ahMap        = function(dataTreePath){return(!cache)?null: cache[JSON.stringify(dataTreePath)].hamap;};
	pblc.joinedTables = function(dataTreePath){return(!cache)?null: cache[JSON.stringify(dataTreePath)].targets;};
	pblc.init = init;
	pblc.getTableAtt = getTableAtt;
	pblc.getJoinedTables = getJoinedTables;
	pblc.getUserGoodie = getUserGoodie;
	pblc.setJobColumns = setJobColumns;
	pblc.test = test;
	return pblc;	
}();

/**
 * Modalinfo extension opening a popup with a Simbad summary for the given solution
 */
Modalinfo.simbad = function (pos) {
	Processing.show("Waiting on Simbad Response");
	$.getJSON("simbadtooltip", {pos: pos}, function(jsdata) {
		Processing.hide();
		if( Processing.jsonError(jsdata, "Simbad Tooltip Failure") ) {
			return;
		} else {
			var table = "";
			if( jsdata.aaData.length > 0 ) {
				table += ("<img src='http://alasky.u-strasbg.fr/cgi/simbad-thumbnails/get-thumbnail.py?name=" 
						+ encodeURIComponent((jsdata.aaData[0])[0]) + "'/>");
			} else {
				table +="<span class='help'>No vignette available</span>";
			}
			table += "<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\"  id=\"simbadtable\" class=\"display\"></table>";
			Modalinfo.dataPanel("Simbad Summary for Position " 
					+ pos 
					+ "<a class=simbad target=blank href=\"http://simbad.u-strasbg.fr/simbad/sim-coo?Radius=1&Coord=" 
					+ encodeURIComponent(pos) + "\"></a>"
					, table);
			$('#simbadtable').dataTable({
				"aoColumns" : jsdata.aoColumns,
				"aaData" : jsdata.aaData,
				"sDom": '<"#simbadtable_banner"pif>t',
				"bPaginate" : true,
				"aaSorting" : [],
				"bSort" : false,
				"bFilter" : true,
				"bAutoWidth" : true,
				"bDestroy" : true
			});
			$("#simbadtable_banner").css("font-size","small");
			$("#simbadtable_info").css("width","30%");
		}
	});
};


/**		
 * Opens thje region editor
 * The data passed to the  handler look like that:
		    {isReady: true,             // true if the polygone is closed
		    userAction: userAction,     // handler called after the user have clicked on Accept
		    region : {
		        format: "array2dim",    // The only one suported yet [[x, y]....]
		        points: this.poligone.skyPositions  // array with structire matching the format
		        size: {x: , y:} // regiosn size in deg
		        }
 * @param handler handler to be called when the polygone is complete ao accepted
 * @param points object denoted the initial value of the polygone {type: ... value:} type is format of the 
 * value (saadaql or array) and value is the data string wich will be parsed
 */
Modalinfo.regionEditor = null;
Modalinfo.region = function (handler, points) {	
	if( $('#aladin-lite-div').length == 0){		
		$(document.documentElement).append('<div id="aladin-lite-div" style="width: 400px; height: 400px"></div>');
		this.regionEditor = new RegionEditor_mVc  ("aladin-lite-div", handler, points); 
		this.regionEditor.init();
		$('#aladin-lite-div').dialog({ modal: true
			, width: 'auto'
				, dialogClass: 'd-maxsize'
					, title: "Sky Region Editor (beta)" 		  
						, zIndex: zIndexModalinfo
		});	
	} else {
		/*
		 * Has already been created, just reopen it
		 */
		$('#aladin-lite-div').dialog('open');
	}
	/*
	 * For the Aladin command panel to be on the top layer: so it is enable to get all events
	 */
	$(".aladin-box").css("z-index", (9999));
	this.regionEditor.setInitialValue(points);
}

/**
 * close the region editor , hide it instead
 */
Modalinfo.closeRegion  = function (){
	$('#aladin-lite-div').dialog('close');
}

/**
 * Used to debug the isseu with multiple aladin instance running at the same time
 */
Modalinfo.regionForAMDebugging = function (handler, points) {		
	$(document.documentElement).append('<div id="aladin-lite-div" style="width: 400px; height: 400px"></div>');
	this.regionEditor = new RegionEditor_mVc  ("aladin-lite-div", handler, points); 
	this.regionEditor.init();
	$('#aladin-lite-div').dialog({ modal: true
		, width: 'auto'
			, dialogClass: 'd-maxsize'
				, title: "title" 		  
					, zIndex: zIndexModalinfo
	});	
	$(".aladin-box").css("z-index", (zIndexModalinfo + 2));

	this.regionEditor.setInitialValue(points);

}




/**
 * Modalinfo extension extension handling a file upload
 * All parameters are required (even null)
 * - title: dialog box title
 * - url: Upload url: This servlet is supposed to return the JSON message which will be displayed
 * - description: HTML text describing the service
 * - handler is called in case of success with the object {path, retour} as parameter
 *   where retour is the object returned by the server: {name, size} usually and path 
 *   results from uploaded filepath parsing {path, filename}
 * - beforeHandler is called before the download starts
 * - extraParamers: [{name, value}]: List of hidden parameters to be set with in addition to the file upload
 */
Modalinfo.uploadForm = function (title, url, description, handler, beforeHandler, extraParamers) {
	var htmlForm = '<form id="uploadPanel" target="_sblank" action="' + url + '" method="post"'
	+  'enctype="multipart/form-data">';
	if( extraParamers != null) {
		for( var i=0 ; i<extraParamers.length ; i++ ) 
			htmlForm += "<input type='hidden'  name='" + extraParamers[i].name + "'  value='" + extraParamers[i].value + "'>";
	}
	htmlForm += ' <input class=stdinput  id="uploadPanel_filename" type="file" name="file" /><br>'
		+ ' <p class=help></p><br>'
		+ '    <input  type="submit" value="Upload" />'
		+ '</form>';	
	Modalinfo.dataPanel(title, htmlForm, null);

	if( description != null ) {
		$('#uploadPanel p').html(description);
	}
	$('form#uploadPanel').ajaxForm({
		beforeSubmit: function() {
			if(beforeHandler != null ) {
				beforeHandler();
			}
		},
		success: function(e) {
			if( Processing.jsonError(e, "Upload Position List Failure") ) {
				Modalinfo.close();
				return;
			} else {
				Out.debug("Upload success: " + JSON.stringify(e));
				if( handler != null) {
					var retour = {retour: e, path : $('#uploadPanel_filename').val().xtractFilename()};
					handler(retour);
				}
			}
		}
	});
};

/**
 * Singleton object wrapping the creation of query editor forms
 */
QueryConstraintEditor = function() {
	var nativeConstraintEditor = function (params /*{parentDivId, formName, getMetaUrl, queryView*/) {
		var parentdiv = $('#' + params.parentDivId);
		if( !parentdiv.length) {
			Modalinfo.error("Div #" + params.parentDivId + " not found");
			return ;
		}
		var view  = new ConstQEditor_mVc(params /*{parentDivId,formName,queryView}*/);
		new ConstQEditor_mvC(view, new ConstQEditor_Mvc());
		view.draw();
		return view;
	};
	var ucdConstraintEditor = function (params /*{parentDivId, formName, queryView, help}*/) {
		var parentdiv = $('#' + params.parentDivId);
		if( !parentdiv.length) {
			Modalinfo.error("Div #" + params.parentDivId + " not found");
			return ;
		}
		var view  = new UcdQEditor_mVc(params /*{parentDivId,formName,queryView, help}*/);
		var mod = new UcdQEditor_Mvc();
		new ConstQEditor_mvC(view, mod);
		view.draw();
		return view;
	};
	var ucdPatternEditor = function (params /*{parentDivId, formName, queryView, relationName, help}*/) {
		var parentdiv = $('#' + params.parentDivId);
		if( !parentdiv.length) {
			Modalinfo.error("Div #" + params.parentDivId + " not found");
			return ;
		}
		var view  = new UcdPatternEditor_mVc(params /*{parentDivId, formName, queryView, help}*/);
		var mod = new UcdPatternEditor_Mvc(params.relationName);
		new ConstQEditor_mvC(view, mod);
		view.draw();
		return view;
	};
	var posConstraintEditor = function (params /*{parentDivId, formName, queryView, frames, urls}*/) {
		var parentdiv = $('#' + params.parentDivId);
		if( !parentdiv.length) {
			Modalinfo.error("Div #" + params.parentDivId + " not found");
			return ;
		}
		var view  = new PosQEditor_mVc(params);
		var mod = new PosQEditor_Mvc();
		new ConstQEditor_mvC(view, mod);
		view.draw();
		return view;
	};
	var simplePosConstraintEditor = function (params /*{parentDivId, formName, queryView, frames, urls}*/) {
		var parentdiv = $('#' + params.parentDivId);
		if( !parentdiv.length) {
			Modalinfo.error("Div #" + params.parentDivId + " not found");
			return ;
		}
		var view =  new SimplePos_mVc(params);
		view.draw();
		return view;
	};
	var catalogueConstraintEditor = function (params /*{parentDivId, formName, getMetaUrl, queryView, relationName, distanceQualifer, help}*/) {
		var parentdiv = $('#' + params.parentDivId);
		if( !parentdiv.length) {
			Modalinfo.error("Div #" + params.parentDivId + " not found");
			return ;
		}
		var view  = new CatalogueQEditor_mVc(params);
		var mod = new CatalogueQEditor_Mvc(params);
		new ConstQEditor_mvC(view, mod);
		view.draw();
		return view;
	};
	var crossidConstraintEditor = function (params /*{parentDivId, formName, getMetaUrl, queryView, relationName, probaQualifier, help}*/) {
		var parentdiv = $('#' + params.parentDivId);
		if( !parentdiv.length) {
			Modalinfo.error("Div #" + params.parentDivId + " not found");
			return ;
		}
		var view  = new CatalogueQEditor_mVc(params);
		var mod = new CrossidQEditor_Mvc(params);
		new ConstQEditor_mvC(view, mod);
		view.draw();
		return view;
	};
	var attachedDataEditor = function(params /*{parentDivId, formName, queryView, title, products}*/) {
		var parentdiv = $('#' + params.parentDivId);
		if( !parentdiv.length) {
			Modalinfo.error("Div #" + params.parentDivId + " not found");
			return ;
		}
		var view =  new AttachedData_mVc(params);
		view.draw();
		return view;
	};
	var vizierKeywordsEditor = function(params /*{parentDivId, formName, queryView, title, getMetaUrl*/) {
		var parentdiv = $('#' + params.parentDivId);
		if( !parentdiv.length) {
			Modalinfo.error("Div #" + params.parentDivId + " not found");
			return ;
		}
		var view =  new VizierKeywords_mVc(params);
		view.draw();
		return view;
	};
	var tapColumnSelector = function (params /*{parentDivId, formName, queryView, currentNode}*/) {
		var parentdiv = $('#' + params.parentDivId);
		if( !parentdiv.length) {
			Modalinfo.error("Div #" +params. parentDivId + " not found");
			return ;
		}
		var view  = new tapColSelector_mVc(params);
		new ConstQEditor_mvC(view, new tapColSelector_Mvc());
		view.draw();
		return view;
	};
	var tapConstraintEditor = function (params /*parentDivId, formName, sesameUrl, upload { url, postHandler}, queryView, currentNode }*/) {
		var parentdiv = $('#' + params.parentDivId);
		if( !parentdiv.length) {
			Modalinfo.error("Div #" + params.parentDivId + " not found");
			return ;
		}
		var view  = new tapQEditor_mVc(params /*parentDivId, formName, sesameUrl, upload { url, postHandler}, queryView, currentNode }*/);
		new ConstQEditor_mvC(view, new tapQEditor_Mvc());
		view.draw();
		return view;
	};
	var queryTextEditor= function (params) {
		var parentdiv = $('#' + params.parentDivId);
		if( !parentdiv.length) {
			Modalinfo.error("Div #" + params.parentDivId + " not found");
			return ;
		}
		var view  = new QueryTextEditor_mVc(params);
		var mod = new QueryTextEditor_Mvc();
		new QueryTextEditor_mvC(view, mod);
		view.draw();
		return view;
		;
	};
	var adqlTextEditor= function (params) {
		var parentdiv = $('#' + params.parentDivId);
		if( !parentdiv.length) {
			Modalinfo.error("Div #" + params.parentDivId + " not found");
			return ;
		}
		var view  = new QueryTextEditor_mVc(params);
		var mod = new ADQLTextEditor_Mvc();
		new QueryTextEditor_mvC(view, mod);
		view.draw();
		return view;
		;
	};
	/*
	 * exports
	 */
	var pblc = {};
	pblc.nativeConstraintEditor = nativeConstraintEditor;
	pblc.ucdConstraintEditor = ucdConstraintEditor;
	pblc.ucdPatternEditor = ucdPatternEditor;
	pblc.posConstraintEditor = posConstraintEditor;
	pblc.simplePosConstraintEditor = simplePosConstraintEditor;
	pblc.attachedDataEditor = attachedDataEditor;
	pblc.vizierKeywordsEditor = vizierKeywordsEditor;
	pblc.catalogueConstraintEditor = catalogueConstraintEditor;
	pblc.crossidConstraintEditor = crossidConstraintEditor;
	pblc.tapConstraintEditor = tapConstraintEditor;
	pblc.tapColumnSelector = tapColumnSelector;
	pblc.queryTextEditor = queryTextEditor;
	pblc.adqlTextEditor = adqlTextEditor;
	return pblc;
}();

/**
 * This object open a dialog box handling the datalink returned by 
 * baseurl. Forwardurl is a proxy url which can be setup to wor aroubf XDomain issues.
 *   in this case, the datalink description is searched at forwardurl + encodeURI(baseurl)
 */
DataLinkBrowser = function() {
	var startBrowser = function (baseurl, forwardurl) {
		var view  = new DataLink_mVc({baseurl: baseurl,forwardurl:forwardurl });
		var mod = new DataLink_Mvc();
		new DataLink_mvC(view, mod);
		view.draw();
		return view;
		;
	};
	/*
	 * exports
	 */
	var pblc = {};
	pblc.startBrowser = startBrowser;
	return pblc;
}();

console.log('=============== >  domain.js ');

/*! jQuery UI - v1.8.22 - 2012-07-24
* https://github.com/jquery/jquery-ui
* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.draggable.js, jquery.ui.droppable.js, jquery.ui.resizable.js, jquery.ui.selectable.js, jquery.ui.sortable.js, jquery.effects.core.js, jquery.effects.blind.js, jquery.effects.bounce.js, jquery.effects.clip.js, jquery.effects.drop.js, jquery.effects.explode.js, jquery.effects.fade.js, jquery.effects.fold.js, jquery.effects.highlight.js, jquery.effects.pulsate.js, jquery.effects.scale.js, jquery.effects.shake.js, jquery.effects.slide.js, jquery.effects.transfer.js, jquery.ui.accordion.js, jquery.ui.autocomplete.js, jquery.ui.button.js, jquery.ui.datepicker.js, jquery.ui.dialog.js, jquery.ui.position.js, jquery.ui.progressbar.js, jquery.ui.slider.js, jquery.ui.tabs.js
* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */

(function( $, undefined ) {

// prevent duplicate loading
// this is only a problem because we proxy existing functions
// and we don't want to double proxy them
$.ui = $.ui || {};
if ( $.ui.version ) {
	return;
}

$.extend( $.ui, {
	version: "1.8.22",

	keyCode: {
		ALT: 18,
		BACKSPACE: 8,
		CAPS_LOCK: 20,
		COMMA: 188,
		COMMAND: 91,
		COMMAND_LEFT: 91, // COMMAND
		COMMAND_RIGHT: 93,
		CONTROL: 17,
		DELETE: 46,
		DOWN: 40,
		END: 35,
		ENTER: 13,
		ESCAPE: 27,
		HOME: 36,
		INSERT: 45,
		LEFT: 37,
		MENU: 93, // COMMAND_RIGHT
		NUMPAD_ADD: 107,
		NUMPAD_DECIMAL: 110,
		NUMPAD_DIVIDE: 111,
		NUMPAD_ENTER: 108,
		NUMPAD_MULTIPLY: 106,
		NUMPAD_SUBTRACT: 109,
		PAGE_DOWN: 34,
		PAGE_UP: 33,
		PERIOD: 190,
		RIGHT: 39,
		SHIFT: 16,
		SPACE: 32,
		TAB: 9,
		UP: 38,
		WINDOWS: 91 // COMMAND
	}
});

// plugins
$.fn.extend({
	propAttr: $.fn.prop || $.fn.attr,

	_focus: $.fn.focus,
	focus: function( delay, fn ) {
		return typeof delay === "number" ?
			this.each(function() {
				var elem = this;
				setTimeout(function() {
					$( elem ).focus();
					if ( fn ) {
						fn.call( elem );
					}
				}, delay );
			}) :
			this._focus.apply( this, arguments );
	},

	scrollParent: function() {
		var scrollParent;
		if (($.browser.msie && (/(static|relative)/).test(this.css('position'))) || (/absolute/).test(this.css('position'))) {
			scrollParent = this.parents().filter(function() {
				return (/(relative|absolute|fixed)/).test($.curCSS(this,'position',1)) && (/(auto|scroll)/).test($.curCSS(this,'overflow',1)+$.curCSS(this,'overflow-y',1)+$.curCSS(this,'overflow-x',1));
			}).eq(0);
		} else {
			scrollParent = this.parents().filter(function() {
				return (/(auto|scroll)/).test($.curCSS(this,'overflow',1)+$.curCSS(this,'overflow-y',1)+$.curCSS(this,'overflow-x',1));
			}).eq(0);
		}

		return (/fixed/).test(this.css('position')) || !scrollParent.length ? $(document) : scrollParent;
	},

	zIndex: function( zIndex ) {
		if ( zIndex !== undefined ) {
			return this.css( "zIndex", zIndex );
		}

		if ( this.length ) {
			var elem = $( this[ 0 ] ), position, value;
			while ( elem.length && elem[ 0 ] !== document ) {
				// Ignore z-index if position is set to a value where z-index is ignored by the browser
				// This makes behavior of this function consistent across browsers
				// WebKit always returns auto if the element is positioned
				position = elem.css( "position" );
				if ( position === "absolute" || position === "relative" || position === "fixed" ) {
					// IE returns 0 when zIndex is not specified
					// other browsers return a string
					// we ignore the case of nested elements with an explicit value of 0
					// <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
					value = parseInt( elem.css( "zIndex" ), 10 );
					if ( !isNaN( value ) && value !== 0 ) {
						return value;
					}
				}
				elem = elem.parent();
			}
		}

		return 0;
	},

	disableSelection: function() {
		return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) +
			".ui-disableSelection", function( event ) {
				event.preventDefault();
			});
	},

	enableSelection: function() {
		return this.unbind( ".ui-disableSelection" );
	}
});

// support: jQuery <1.8
if ( !$( "<a>" ).outerWidth( 1 ).jquery ) {
	$.each( [ "Width", "Height" ], function( i, name ) {
		var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
			type = name.toLowerCase(),
			orig = {
				innerWidth: $.fn.innerWidth,
				innerHeight: $.fn.innerHeight,
				outerWidth: $.fn.outerWidth,
				outerHeight: $.fn.outerHeight
			};

		function reduce( elem, size, border, margin ) {
			$.each( side, function() {
				size -= parseFloat( $.curCSS( elem, "padding" + this, true) ) || 0;
				if ( border ) {
					size -= parseFloat( $.curCSS( elem, "border" + this + "Width", true) ) || 0;
				}
				if ( margin ) {
					size -= parseFloat( $.curCSS( elem, "margin" + this, true) ) || 0;
				}
			});
			return size;
		}

		$.fn[ "inner" + name ] = function( size ) {
			if ( size === undefined ) {
				return orig[ "inner" + name ].call( this );
			}

			return this.each(function() {
				$( this ).css( type, reduce( this, size ) + "px" );
			});
		};

		$.fn[ "outer" + name] = function( size, margin ) {
			if ( typeof size !== "number" ) {
				return orig[ "outer" + name ].call( this, size );
			}

			return this.each(function() {
				$( this).css( type, reduce( this, size, true, margin ) + "px" );
			});
		};
	});
}

// selectors
function focusable( element, isTabIndexNotNaN ) {
	var nodeName = element.nodeName.toLowerCase();
	if ( "area" === nodeName ) {
		var map = element.parentNode,
			mapName = map.name,
			img;
		if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
			return false;
		}
		img = $( "img[usemap=#" + mapName + "]" )[0];
		return !!img && visible( img );
	}
	return ( /input|select|textarea|button|object/.test( nodeName )
		? !element.disabled
		: "a" == nodeName
			? element.href || isTabIndexNotNaN
			: isTabIndexNotNaN)
		// the element and all of its ancestors must be visible
		&& visible( element );
}

function visible( element ) {
	return !$( element ).parents().andSelf().filter(function() {
		return $.curCSS( this, "visibility" ) === "hidden" ||
			$.expr.filters.hidden( this );
	}).length;
}

$.extend( $.expr[ ":" ], {
	data: $.expr.createPseudo ?
		$.expr.createPseudo(function( dataName ) {
			return function( elem ) {
				return !!$.data( elem, dataName );
			};
		}) :
		// support: jQuery <1.8
		function( elem, i, match ) {
			return !!$.data( elem, match[ 3 ] );
		},

	focusable: function( element ) {
		return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
	},

	tabbable: function( element ) {
		var tabIndex = $.attr( element, "tabindex" ),
			isTabIndexNaN = isNaN( tabIndex );
		return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
	}
});

// support
$(function() {
	var body = document.body,
		div = body.appendChild( div = document.createElement( "div" ) );

	// access offsetHeight before setting the style to prevent a layout bug
	// in IE 9 which causes the elemnt to continue to take up space even
	// after it is removed from the DOM (#8026)
	div.offsetHeight;

	$.extend( div.style, {
		minHeight: "100px",
		height: "auto",
		padding: 0,
		borderWidth: 0
	});

	$.support.minHeight = div.offsetHeight === 100;
	$.support.selectstart = "onselectstart" in div;

	// set display to none to avoid a layout bug in IE
	// http://dev.jquery.com/ticket/4014
	body.removeChild( div ).style.display = "none";
});

// jQuery <1.4.3 uses curCSS, in 1.4.3 - 1.7.2 curCSS = css, 1.8+ only has css
if ( !$.curCSS ) {
	$.curCSS = $.css;
}





// deprecated
$.extend( $.ui, {
	// $.ui.plugin is deprecated.  Use the proxy pattern instead.
	plugin: {
		add: function( module, option, set ) {
			var proto = $.ui[ module ].prototype;
			for ( var i in set ) {
				proto.plugins[ i ] = proto.plugins[ i ] || [];
				proto.plugins[ i ].push( [ option, set[ i ] ] );
			}
		},
		call: function( instance, name, args ) {
			var set = instance.plugins[ name ];
			if ( !set || !instance.element[ 0 ].parentNode ) {
				return;
			}
	
			for ( var i = 0; i < set.length; i++ ) {
				if ( instance.options[ set[ i ][ 0 ] ] ) {
					set[ i ][ 1 ].apply( instance.element, args );
				}
			}
		}
	},
	
	// will be deprecated when we switch to jQuery 1.4 - use jQuery.contains()
	contains: function( a, b ) {
		return document.compareDocumentPosition ?
			a.compareDocumentPosition( b ) & 16 :
			a !== b && a.contains( b );
	},
	
	// only used by resizable
	hasScroll: function( el, a ) {
	
		//If overflow is hidden, the element might have extra content, but the user wants to hide it
		if ( $( el ).css( "overflow" ) === "hidden") {
			return false;
		}
	
		var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop",
			has = false;
	
		if ( el[ scroll ] > 0 ) {
			return true;
		}
	
		// TODO: determine which cases actually cause this to happen
		// if the element doesn't have the scroll set, see if it's possible to
		// set the scroll
		el[ scroll ] = 1;
		has = ( el[ scroll ] > 0 );
		el[ scroll ] = 0;
		return has;
	},
	
	// these are odd functions, fix the API or move into individual plugins
	isOverAxis: function( x, reference, size ) {
		//Determines when x coordinate is over "b" element axis
		return ( x > reference ) && ( x < ( reference + size ) );
	},
	isOver: function( y, x, top, left, height, width ) {
		//Determines when x, y coordinates is over "b" element
		return $.ui.isOverAxis( y, top, height ) && $.ui.isOverAxis( x, left, width );
	}
});

})( jQuery );

(function( $, undefined ) {

// jQuery 1.4+
if ( $.cleanData ) {
	var _cleanData = $.cleanData;
	$.cleanData = function( elems ) {
		for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
			try {
				$( elem ).triggerHandler( "remove" );
			// http://bugs.jquery.com/ticket/8235
			} catch( e ) {}
		}
		_cleanData( elems );
	};
} else {
	var _remove = $.fn.remove;
	$.fn.remove = function( selector, keepData ) {
		return this.each(function() {
			if ( !keepData ) {
				if ( !selector || $.filter( selector, [ this ] ).length ) {
					$( "*", this ).add( [ this ] ).each(function() {
						try {
							$( this ).triggerHandler( "remove" );
						// http://bugs.jquery.com/ticket/8235
						} catch( e ) {}
					});
				}
			}
			return _remove.call( $(this), selector, keepData );
		});
	};
}

$.widget = function( name, base, prototype ) {
	var namespace = name.split( "." )[ 0 ],
		fullName;
	name = name.split( "." )[ 1 ];
	fullName = namespace + "-" + name;

	if ( !prototype ) {
		prototype = base;
		base = $.Widget;
	}

	// create selector for plugin
	$.expr[ ":" ][ fullName ] = function( elem ) {
		return !!$.data( elem, name );
	};

	$[ namespace ] = $[ namespace ] || {};
	$[ namespace ][ name ] = function( options, element ) {
		// allow instantiation without initializing for simple inheritance
		if ( arguments.length ) {
			this._createWidget( options, element );
		}
	};

	var basePrototype = new base();
	// we need to make the options hash a property directly on the new instance
	// otherwise we'll modify the options hash on the prototype that we're
	// inheriting from
//	$.each( basePrototype, function( key, val ) {
//		if ( $.isPlainObject(val) ) {
//			basePrototype[ key ] = $.extend( {}, val );
//		}
//	});
	basePrototype.options = $.extend( true, {}, basePrototype.options );
	$[ namespace ][ name ].prototype = $.extend( true, basePrototype, {
		namespace: namespace,
		widgetName: name,
		widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name,
		widgetBaseClass: fullName
	}, prototype );

	$.widget.bridge( name, $[ namespace ][ name ] );
};

$.widget.bridge = function( name, object ) {
	$.fn[ name ] = function( options ) {
		var isMethodCall = typeof options === "string",
			args = Array.prototype.slice.call( arguments, 1 ),
			returnValue = this;

		// allow multiple hashes to be passed on init
		options = !isMethodCall && args.length ?
			$.extend.apply( null, [ true, options ].concat(args) ) :
			options;

		// prevent calls to internal methods
		if ( isMethodCall && options.charAt( 0 ) === "_" ) {
			return returnValue;
		}

		if ( isMethodCall ) {
			this.each(function() {
				var instance = $.data( this, name ),
					methodValue = instance && $.isFunction( instance[options] ) ?
						instance[ options ].apply( instance, args ) :
						instance;
				// TODO: add this back in 1.9 and use $.error() (see #5972)
//				if ( !instance ) {
//					throw "cannot call methods on " + name + " prior to initialization; " +
//						"attempted to call method '" + options + "'";
//				}
//				if ( !$.isFunction( instance[options] ) ) {
//					throw "no such method '" + options + "' for " + name + " widget instance";
//				}
//				var methodValue = instance[ options ].apply( instance, args );
				if ( methodValue !== instance && methodValue !== undefined ) {
					returnValue = methodValue;
					return false;
				}
			});
		} else {
			this.each(function() {
				var instance = $.data( this, name );
				if ( instance ) {
					instance.option( options || {} )._init();
				} else {
					$.data( this, name, new object( options, this ) );
				}
			});
		}

		return returnValue;
	};
};

$.Widget = function( options, element ) {
	// allow instantiation without initializing for simple inheritance
	if ( arguments.length ) {
		this._createWidget( options, element );
	}
};

$.Widget.prototype = {
	widgetName: "widget",
	widgetEventPrefix: "",
	options: {
		disabled: false
	},
	_createWidget: function( options, element ) {
		// $.widget.bridge stores the plugin instance, but we do it anyway
		// so that it's stored even before the _create function runs
		$.data( element, this.widgetName, this );
		this.element = $( element );
		this.options = $.extend( true, {},
			this.options,
			this._getCreateOptions(),
			options );

		var self = this;
		this.element.bind( "remove." + this.widgetName, function() {
			self.destroy();
		});

		this._create();
		this._trigger( "create" );
		this._init();
	},
	_getCreateOptions: function() {
		return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ];
	},
	_create: function() {},
	_init: function() {},

	destroy: function() {
		this.element
			.unbind( "." + this.widgetName )
			.removeData( this.widgetName );
		this.widget()
			.unbind( "." + this.widgetName )
			.removeAttr( "aria-disabled" )
			.removeClass(
				this.widgetBaseClass + "-disabled " +
				"ui-state-disabled" );
	},

	widget: function() {
		return this.element;
	},

	option: function( key, value ) {
		var options = key;

		if ( arguments.length === 0 ) {
			// don't return a reference to the internal hash
			return $.extend( {}, this.options );
		}

		if  (typeof key === "string" ) {
			if ( value === undefined ) {
				return this.options[ key ];
			}
			options = {};
			options[ key ] = value;
		}

		this._setOptions( options );

		return this;
	},
	_setOptions: function( options ) {
		var self = this;
		$.each( options, function( key, value ) {
			self._setOption( key, value );
		});

		return this;
	},
	_setOption: function( key, value ) {
		this.options[ key ] = value;

		if ( key === "disabled" ) {
			this.widget()
				[ value ? "addClass" : "removeClass"](
					this.widgetBaseClass + "-disabled" + " " +
					"ui-state-disabled" )
				.attr( "aria-disabled", value );
		}

		return this;
	},

	enable: function() {
		return this._setOption( "disabled", false );
	},
	disable: function() {
		return this._setOption( "disabled", true );
	},

	_trigger: function( type, event, data ) {
		var prop, orig,
			callback = this.options[ type ];

		data = data || {};
		event = $.Event( event );
		event.type = ( type === this.widgetEventPrefix ?
			type :
			this.widgetEventPrefix + type ).toLowerCase();
		// the original event may come from any element
		// so we need to reset the target on the new event
		event.target = this.element[ 0 ];

		// copy original event properties over to the new event
		orig = event.originalEvent;
		if ( orig ) {
			for ( prop in orig ) {
				if ( !( prop in event ) ) {
					event[ prop ] = orig[ prop ];
				}
			}
		}

		this.element.trigger( event, data );

		return !( $.isFunction(callback) &&
			callback.call( this.element[0], event, data ) === false ||
			event.isDefaultPrevented() );
	}
};

})( jQuery );

(function( $, undefined ) {

var mouseHandled = false;
$( document ).mouseup( function( e ) {
	mouseHandled = false;
});

$.widget("ui.mouse", {
	options: {
		cancel: ':input,option',
		distance: 1,
		delay: 0
	},
	_mouseInit: function() {
		var self = this;

		this.element
			.bind('mousedown.'+this.widgetName, function(event) {
				return self._mouseDown(event);
			})
			.bind('click.'+this.widgetName, function(event) {
				if (true === $.data(event.target, self.widgetName + '.preventClickEvent')) {
				    $.removeData(event.target, self.widgetName + '.preventClickEvent');
					event.stopImmediatePropagation();
					return false;
				}
			});

		this.started = false;
	},

	// TODO: make sure destroying one instance of mouse doesn't mess with
	// other instances of mouse
	_mouseDestroy: function() {
		this.element.unbind('.'+this.widgetName);
		$(document)
			.unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
			.unbind('mouseup.'+this.widgetName, this._mouseUpDelegate);
	},

	_mouseDown: function(event) {
		// don't let more than one widget handle mouseStart
		if( mouseHandled ) { return };

		// we may have missed mouseup (out of window)
		(this._mouseStarted && this._mouseUp(event));

		this._mouseDownEvent = event;

		var self = this,
			btnIsLeft = (event.which == 1),
			// event.target.nodeName works around a bug in IE 8 with
			// disabled inputs (#7620)
			elIsCancel = (typeof this.options.cancel == "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false);
		if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
			return true;
		}

		this.mouseDelayMet = !this.options.delay;
		if (!this.mouseDelayMet) {
			this._mouseDelayTimer = setTimeout(function() {
				self.mouseDelayMet = true;
			}, this.options.delay);
		}

		if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
			this._mouseStarted = (this._mouseStart(event) !== false);
			if (!this._mouseStarted) {
				event.preventDefault();
				return true;
			}
		}

		// Click event may never have fired (Gecko & Opera)
		if (true === $.data(event.target, this.widgetName + '.preventClickEvent')) {
			$.removeData(event.target, this.widgetName + '.preventClickEvent');
		}

		// these delegates are required to keep context
		this._mouseMoveDelegate = function(event) {
			return self._mouseMove(event);
		};
		this._mouseUpDelegate = function(event) {
			return self._mouseUp(event);
		};
		$(document)
			.bind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
			.bind('mouseup.'+this.widgetName, this._mouseUpDelegate);

		event.preventDefault();
		
		mouseHandled = true;
		return true;
	},

	_mouseMove: function(event) {
		// IE mouseup check - mouseup happened when mouse was out of window
		if ($.browser.msie && !(document.documentMode >= 9) && !event.button) {
			return this._mouseUp(event);
		}

		if (this._mouseStarted) {
			this._mouseDrag(event);
			return event.preventDefault();
		}

		if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
			this._mouseStarted =
				(this._mouseStart(this._mouseDownEvent, event) !== false);
			(this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event));
		}

		return !this._mouseStarted;
	},

	_mouseUp: function(event) {
		$(document)
			.unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
			.unbind('mouseup.'+this.widgetName, this._mouseUpDelegate);

		if (this._mouseStarted) {
			this._mouseStarted = false;

			if (event.target == this._mouseDownEvent.target) {
			    $.data(event.target, this.widgetName + '.preventClickEvent', true);
			}

			this._mouseStop(event);
		}

		return false;
	},

	_mouseDistanceMet: function(event) {
		return (Math.max(
				Math.abs(this._mouseDownEvent.pageX - event.pageX),
				Math.abs(this._mouseDownEvent.pageY - event.pageY)
			) >= this.options.distance
		);
	},

	_mouseDelayMet: function(event) {
		return this.mouseDelayMet;
	},

	// These are placeholder methods, to be overriden by extending plugin
	_mouseStart: function(event) {},
	_mouseDrag: function(event) {},
	_mouseStop: function(event) {},
	_mouseCapture: function(event) { return true; }
});

})(jQuery);

(function( $, undefined ) {

$.widget("ui.draggable", $.ui.mouse, {
	widgetEventPrefix: "drag",
	options: {
		addClasses: true,
		appendTo: "parent",
		axis: false,
		connectToSortable: false,
		containment: false,
		cursor: "auto",
		cursorAt: false,
		grid: false,
		handle: false,
		helper: "original",
		iframeFix: false,
		opacity: false,
		refreshPositions: false,
		revert: false,
		revertDuration: 500,
		scope: "default",
		scroll: true,
		scrollSensitivity: 20,
		scrollSpeed: 20,
		snap: false,
		snapMode: "both",
		snapTolerance: 20,
		stack: false,
		zIndex: false
	},
	_create: function() {

		if (this.options.helper == 'original' && !(/^(?:r|a|f)/).test(this.element.css("position")))
			this.element[0].style.position = 'relative';

		(this.options.addClasses && this.element.addClass("ui-draggable"));
		(this.options.disabled && this.element.addClass("ui-draggable-disabled"));

		this._mouseInit();

	},

	destroy: function() {
		if(!this.element.data('draggable')) return;
		this.element
			.removeData("draggable")
			.unbind(".draggable")
			.removeClass("ui-draggable"
				+ " ui-draggable-dragging"
				+ " ui-draggable-disabled");
		this._mouseDestroy();

		return this;
	},

	_mouseCapture: function(event) {

		var o = this.options;

		// among others, prevent a drag on a resizable-handle
		if (this.helper || o.disabled || $(event.target).is('.ui-resizable-handle'))
			return false;

		//Quit if we're not on a valid handle
		this.handle = this._getHandle(event);
		if (!this.handle)
			return false;
		
		if ( o.iframeFix ) {
			$(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() {
				$('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>')
				.css({
					width: this.offsetWidth+"px", height: this.offsetHeight+"px",
					position: "absolute", opacity: "0.001", zIndex: 1000
				})
				.css($(this).offset())
				.appendTo("body");
			});
		}

		return true;

	},

	_mouseStart: function(event) {

		var o = this.options;

		//Create and append the visible helper
		this.helper = this._createHelper(event);

		this.helper.addClass("ui-draggable-dragging");

		//Cache the helper size
		this._cacheHelperProportions();

		//If ddmanager is used for droppables, set the global draggable
		if($.ui.ddmanager)
			$.ui.ddmanager.current = this;

		/*
		 * - Position generation -
		 * This block generates everything position related - it's the core of draggables.
		 */

		//Cache the margins of the original element
		this._cacheMargins();

		//Store the helper's css position
		this.cssPosition = this.helper.css("position");
		this.scrollParent = this.helper.scrollParent();

		//The element's absolute position on the page minus margins
		this.offset = this.positionAbs = this.element.offset();
		this.offset = {
			top: this.offset.top - this.margins.top,
			left: this.offset.left - this.margins.left
		};

		$.extend(this.offset, {
			click: { //Where the click happened, relative to the element
				left: event.pageX - this.offset.left,
				top: event.pageY - this.offset.top
			},
			parent: this._getParentOffset(),
			relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
		});

		//Generate the original position
		this.originalPosition = this.position = this._generatePosition(event);
		this.originalPageX = event.pageX;
		this.originalPageY = event.pageY;

		//Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
		(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));

		//Set a containment if given in the options
		if(o.containment)
			this._setContainment();

		//Trigger event + callbacks
		if(this._trigger("start", event) === false) {
			this._clear();
			return false;
		}

		//Recache the helper size
		this._cacheHelperProportions();

		//Prepare the droppable offsets
		if ($.ui.ddmanager && !o.dropBehaviour)
			$.ui.ddmanager.prepareOffsets(this, event);

		
		this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position
		
		//If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003)
		if ( $.ui.ddmanager ) $.ui.ddmanager.dragStart(this, event);
		
		return true;
	},

	_mouseDrag: function(event, noPropagation) {

		//Compute the helpers position
		this.position = this._generatePosition(event);
		this.positionAbs = this._convertPositionTo("absolute");

		//Call plugins and callbacks and use the resulting position if something is returned
		if (!noPropagation) {
			var ui = this._uiHash();
			if(this._trigger('drag', event, ui) === false) {
				this._mouseUp({});
				return false;
			}
			this.position = ui.position;
		}

		if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
		if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
		if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);

		return false;
	},

	_mouseStop: function(event) {

		//If we are using droppables, inform the manager about the drop
		var dropped = false;
		if ($.ui.ddmanager && !this.options.dropBehaviour)
			dropped = $.ui.ddmanager.drop(this, event);

		//if a drop comes from outside (a sortable)
		if(this.dropped) {
			dropped = this.dropped;
			this.dropped = false;
		}
		
		//if the original element is no longer in the DOM don't bother to continue (see #8269)
		var element = this.element[0], elementInDom = false;
		while ( element && (element = element.parentNode) ) {
			if (element == document ) {
				elementInDom = true;
			}
		}
		if ( !elementInDom && this.options.helper === "original" )
			return false;

		if((this.options.revert == "invalid" && !dropped) || (this.options.revert == "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) {
			var self = this;
			$(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() {
				if(self._trigger("stop", event) !== false) {
					self._clear();
				}
			});
		} else {
			if(this._trigger("stop", event) !== false) {
				this._clear();
			}
		}

		return false;
	},
	
	_mouseUp: function(event) {
		if (this.options.iframeFix === true) {
			$("div.ui-draggable-iframeFix").each(function() { 
				this.parentNode.removeChild(this); 
			}); //Remove frame helpers
		}
		
		//If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003)
		if( $.ui.ddmanager ) $.ui.ddmanager.dragStop(this, event);
		
		return $.ui.mouse.prototype._mouseUp.call(this, event);
	},
	
	cancel: function() {
		
		if(this.helper.is(".ui-draggable-dragging")) {
			this._mouseUp({});
		} else {
			this._clear();
		}
		
		return this;
		
	},

	_getHandle: function(event) {

		var handle = !this.options.handle || !$(this.options.handle, this.element).length ? true : false;
		$(this.options.handle, this.element)
			.find("*")
			.andSelf()
			.each(function() {
				if(this == event.target) handle = true;
			});

		return handle;

	},

	_createHelper: function(event) {

		var o = this.options;
		var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event])) : (o.helper == 'clone' ? this.element.clone().removeAttr('id') : this.element);

		if(!helper.parents('body').length)
			helper.appendTo((o.appendTo == 'parent' ? this.element[0].parentNode : o.appendTo));

		if(helper[0] != this.element[0] && !(/(fixed|absolute)/).test(helper.css("position")))
			helper.css("position", "absolute");

		return helper;

	},

	_adjustOffsetFromHelper: function(obj) {
		if (typeof obj == 'string') {
			obj = obj.split(' ');
		}
		if ($.isArray(obj)) {
			obj = {left: +obj[0], top: +obj[1] || 0};
		}
		if ('left' in obj) {
			this.offset.click.left = obj.left + this.margins.left;
		}
		if ('right' in obj) {
			this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
		}
		if ('top' in obj) {
			this.offset.click.top = obj.top + this.margins.top;
		}
		if ('bottom' in obj) {
			this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
		}
	},

	_getParentOffset: function() {

		//Get the offsetParent and cache its position
		this.offsetParent = this.helper.offsetParent();
		var po = this.offsetParent.offset();

		// This is a special case where we need to modify a offset calculated on start, since the following happened:
		// 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
		// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
		//    the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
		if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) {
			po.left += this.scrollParent.scrollLeft();
			po.top += this.scrollParent.scrollTop();
		}

		if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information
		|| (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.browser.msie)) //Ugly IE fix
			po = { top: 0, left: 0 };

		return {
			top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
			left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
		};

	},

	_getRelativeOffset: function() {

		if(this.cssPosition == "relative") {
			var p = this.element.position();
			return {
				top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
				left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
			};
		} else {
			return { top: 0, left: 0 };
		}

	},

	_cacheMargins: function() {
		this.margins = {
			left: (parseInt(this.element.css("marginLeft"),10) || 0),
			top: (parseInt(this.element.css("marginTop"),10) || 0),
			right: (parseInt(this.element.css("marginRight"),10) || 0),
			bottom: (parseInt(this.element.css("marginBottom"),10) || 0)
		};
	},

	_cacheHelperProportions: function() {
		this.helperProportions = {
			width: this.helper.outerWidth(),
			height: this.helper.outerHeight()
		};
	},

	_setContainment: function() {

		var o = this.options;
		if(o.containment == 'parent') o.containment = this.helper[0].parentNode;
		if(o.containment == 'document' || o.containment == 'window') this.containment = [
			o.containment == 'document' ? 0 : $(window).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
			o.containment == 'document' ? 0 : $(window).scrollTop() - this.offset.relative.top - this.offset.parent.top,
			(o.containment == 'document' ? 0 : $(window).scrollLeft()) + $(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left,
			(o.containment == 'document' ? 0 : $(window).scrollTop()) + ($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
		];

		if(!(/^(document|window|parent)$/).test(o.containment) && o.containment.constructor != Array) {
		        var c = $(o.containment);
			var ce = c[0]; if(!ce) return;
			var co = c.offset();
			var over = ($(ce).css("overflow") != 'hidden');

			this.containment = [
				(parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0),
				(parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0),
				(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left - this.margins.right,
				(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top  - this.margins.bottom
			];
			this.relative_container = c;

		} else if(o.containment.constructor == Array) {
			this.containment = o.containment;
		}

	},

	_convertPositionTo: function(d, pos) {

		if(!pos) pos = this.position;
		var mod = d == "absolute" ? 1 : -1;
		var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);

		return {
			top: (
				pos.top																	// The absolute mouse position
				+ this.offset.relative.top * mod										// Only for relative positioned nodes: Relative offset from element to offset parent
				+ this.offset.parent.top * mod											// The offsetParent's offset without borders (offset + border)
				- ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
			),
			left: (
				pos.left																// The absolute mouse position
				+ this.offset.relative.left * mod										// Only for relative positioned nodes: Relative offset from element to offset parent
				+ this.offset.parent.left * mod											// The offsetParent's offset without borders (offset + border)
				- ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
			)
		};

	},

	_generatePosition: function(event) {

		var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
		var pageX = event.pageX;
		var pageY = event.pageY;

		/*
		 * - Position constraining -
		 * Constrain the position to a mix of grid, containment.
		 */

		if(this.originalPosition) { //If we are not dragging yet, we won't check for options
		         var containment;
		         if(this.containment) {
				 if (this.relative_container){
				     var co = this.relative_container.offset();
				     containment = [ this.containment[0] + co.left,
						     this.containment[1] + co.top,
						     this.containment[2] + co.left,
						     this.containment[3] + co.top ];
				 }
				 else {
				     containment = this.containment;
				 }

				if(event.pageX - this.offset.click.left < containment[0]) pageX = containment[0] + this.offset.click.left;
				if(event.pageY - this.offset.click.top < containment[1]) pageY = containment[1] + this.offset.click.top;
				if(event.pageX - this.offset.click.left > containment[2]) pageX = containment[2] + this.offset.click.left;
				if(event.pageY - this.offset.click.top > containment[3]) pageY = containment[3] + this.offset.click.top;
			}

			if(o.grid) {
				//Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950)
				var top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY;
				pageY = containment ? (!(top - this.offset.click.top < containment[1] || top - this.offset.click.top > containment[3]) ? top : (!(top - this.offset.click.top < containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;

				var left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX;
				pageX = containment ? (!(left - this.offset.click.left < containment[0] || left - this.offset.click.left > containment[2]) ? left : (!(left - this.offset.click.left < containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
			}

		}

		return {
			top: (
				pageY																// The absolute mouse position
				- this.offset.click.top													// Click offset (relative to the element)
				- this.offset.relative.top												// Only for relative positioned nodes: Relative offset from element to offset parent
				- this.offset.parent.top												// The offsetParent's offset without borders (offset + border)
				+ ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
			),
			left: (
				pageX																// The absolute mouse position
				- this.offset.click.left												// Click offset (relative to the element)
				- this.offset.relative.left												// Only for relative positioned nodes: Relative offset from element to offset parent
				- this.offset.parent.left												// The offsetParent's offset without borders (offset + border)
				+ ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
			)
		};

	},

	_clear: function() {
		this.helper.removeClass("ui-draggable-dragging");
		if(this.helper[0] != this.element[0] && !this.cancelHelperRemoval) this.helper.remove();
		//if($.ui.ddmanager) $.ui.ddmanager.current = null;
		this.helper = null;
		this.cancelHelperRemoval = false;
	},

	// From now on bulk stuff - mainly helpers

	_trigger: function(type, event, ui) {
		ui = ui || this._uiHash();
		$.ui.plugin.call(this, type, [event, ui]);
		if(type == "drag") this.positionAbs = this._convertPositionTo("absolute"); //The absolute position has to be recalculated after plugins
		return $.Widget.prototype._trigger.call(this, type, event, ui);
	},

	plugins: {},

	_uiHash: function(event) {
		return {
			helper: this.helper,
			position: this.position,
			originalPosition: this.originalPosition,
			offset: this.positionAbs
		};
	}

});

$.extend($.ui.draggable, {
	version: "1.8.22"
});

$.ui.plugin.add("draggable", "connectToSortable", {
	start: function(event, ui) {

		var inst = $(this).data("draggable"), o = inst.options,
			uiSortable = $.extend({}, ui, { item: inst.element });
		inst.sortables = [];
		$(o.connectToSortable).each(function() {
			var sortable = $.data(this, 'sortable');
			if (sortable && !sortable.options.disabled) {
				inst.sortables.push({
					instance: sortable,
					shouldRevert: sortable.options.revert
				});
				sortable.refreshPositions();	// Call the sortable's refreshPositions at drag start to refresh the containerCache since the sortable container cache is used in drag and needs to be up to date (this will ensure it's initialised as well as being kept in step with any changes that might have happened on the page).
				sortable._trigger("activate", event, uiSortable);
			}
		});

	},
	stop: function(event, ui) {

		//If we are still over the sortable, we fake the stop event of the sortable, but also remove helper
		var inst = $(this).data("draggable"),
			uiSortable = $.extend({}, ui, { item: inst.element });

		$.each(inst.sortables, function() {
			if(this.instance.isOver) {

				this.instance.isOver = 0;

				inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance
				this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work)

				//The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: 'valid/invalid'
				if(this.shouldRevert) this.instance.options.revert = true;

				//Trigger the stop of the sortable
				this.instance._mouseStop(event);

				this.instance.options.helper = this.instance.options._helper;

				//If the helper has been the original item, restore properties in the sortable
				if(inst.options.helper == 'original')
					this.instance.currentItem.css({ top: 'auto', left: 'auto' });

			} else {
				this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance
				this.instance._trigger("deactivate", event, uiSortable);
			}

		});

	},
	drag: function(event, ui) {

		var inst = $(this).data("draggable"), self = this;

		var checkPos = function(o) {
			var dyClick = this.offset.click.top, dxClick = this.offset.click.left;
			var helperTop = this.positionAbs.top, helperLeft = this.positionAbs.left;
			var itemHeight = o.height, itemWidth = o.width;
			var itemTop = o.top, itemLeft = o.left;

			return $.ui.isOver(helperTop + dyClick, helperLeft + dxClick, itemTop, itemLeft, itemHeight, itemWidth);
		};

		$.each(inst.sortables, function(i) {
			
			//Copy over some variables to allow calling the sortable's native _intersectsWith
			this.instance.positionAbs = inst.positionAbs;
			this.instance.helperProportions = inst.helperProportions;
			this.instance.offset.click = inst.offset.click;
			
			if(this.instance._intersectsWith(this.instance.containerCache)) {

				//If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once
				if(!this.instance.isOver) {

					this.instance.isOver = 1;
					//Now we fake the start of dragging for the sortable instance,
					//by cloning the list group item, appending it to the sortable and using it as inst.currentItem
					//We can then fire the start event of the sortable with our passed browser event, and our own helper (so it doesn't create a new one)
					this.instance.currentItem = $(self).clone().removeAttr('id').appendTo(this.instance.element).data("sortable-item", true);
					this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it
					this.instance.options.helper = function() { return ui.helper[0]; };

					event.target = this.instance.currentItem[0];
					this.instance._mouseCapture(event, true);
					this.instance._mouseStart(event, true, true);

					//Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes
					this.instance.offset.click.top = inst.offset.click.top;
					this.instance.offset.click.left = inst.offset.click.left;
					this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left;
					this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top;

					inst._trigger("toSortable", event);
					inst.dropped = this.instance.element; //draggable revert needs that
					//hack so receive/update callbacks work (mostly)
					inst.currentItem = inst.element;
					this.instance.fromOutside = inst;

				}

				//Provided we did all the previous steps, we can fire the drag event of the sortable on every draggable drag, when it intersects with the sortable
				if(this.instance.currentItem) this.instance._mouseDrag(event);

			} else {

				//If it doesn't intersect with the sortable, and it intersected before,
				//we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval
				if(this.instance.isOver) {

					this.instance.isOver = 0;
					this.instance.cancelHelperRemoval = true;
					
					//Prevent reverting on this forced stop
					this.instance.options.revert = false;
					
					// The out event needs to be triggered independently
					this.instance._trigger('out', event, this.instance._uiHash(this.instance));
					
					this.instance._mouseStop(event, true);
					this.instance.options.helper = this.instance.options._helper;

					//Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size
					this.instance.currentItem.remove();
					if(this.instance.placeholder) this.instance.placeholder.remove();

					inst._trigger("fromSortable", event);
					inst.dropped = false; //draggable revert needs that
				}

			};

		});

	}
});

$.ui.plugin.add("draggable", "cursor", {
	start: function(event, ui) {
		var t = $('body'), o = $(this).data('draggable').options;
		if (t.css("cursor")) o._cursor = t.css("cursor");
		t.css("cursor", o.cursor);
	},
	stop: function(event, ui) {
		var o = $(this).data('draggable').options;
		if (o._cursor) $('body').css("cursor", o._cursor);
	}
});

$.ui.plugin.add("draggable", "opacity", {
	start: function(event, ui) {
		var t = $(ui.helper), o = $(this).data('draggable').options;
		if(t.css("opacity")) o._opacity = t.css("opacity");
		t.css('opacity', o.opacity);
	},
	stop: function(event, ui) {
		var o = $(this).data('draggable').options;
		if(o._opacity) $(ui.helper).css('opacity', o._opacity);
	}
});

$.ui.plugin.add("draggable", "scroll", {
	start: function(event, ui) {
		var i = $(this).data("draggable");
		if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') i.overflowOffset = i.scrollParent.offset();
	},
	drag: function(event, ui) {

		var i = $(this).data("draggable"), o = i.options, scrolled = false;

		if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') {

			if(!o.axis || o.axis != 'x') {
				if((i.overflowOffset.top + i.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
					i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop + o.scrollSpeed;
				else if(event.pageY - i.overflowOffset.top < o.scrollSensitivity)
					i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop - o.scrollSpeed;
			}

			if(!o.axis || o.axis != 'y') {
				if((i.overflowOffset.left + i.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
					i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft + o.scrollSpeed;
				else if(event.pageX - i.overflowOffset.left < o.scrollSensitivity)
					i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft - o.scrollSpeed;
			}

		} else {

			if(!o.axis || o.axis != 'x') {
				if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
					scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
				else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
					scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
			}

			if(!o.axis || o.axis != 'y') {
				if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
					scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
				else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
					scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
			}

		}

		if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
			$.ui.ddmanager.prepareOffsets(i, event);

	}
});

$.ui.plugin.add("draggable", "snap", {
	start: function(event, ui) {

		var i = $(this).data("draggable"), o = i.options;
		i.snapElements = [];

		$(o.snap.constructor != String ? ( o.snap.items || ':data(draggable)' ) : o.snap).each(function() {
			var $t = $(this); var $o = $t.offset();
			if(this != i.element[0]) i.snapElements.push({
				item: this,
				width: $t.outerWidth(), height: $t.outerHeight(),
				top: $o.top, left: $o.left
			});
		});

	},
	drag: function(event, ui) {

		var inst = $(this).data("draggable"), o = inst.options;
		var d = o.snapTolerance;

		var x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
			y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;

		for (var i = inst.snapElements.length - 1; i >= 0; i--){

			var l = inst.snapElements[i].left, r = l + inst.snapElements[i].width,
				t = inst.snapElements[i].top, b = t + inst.snapElements[i].height;

			//Yes, I know, this is insane ;)
			if(!((l-d < x1 && x1 < r+d && t-d < y1 && y1 < b+d) || (l-d < x1 && x1 < r+d && t-d < y2 && y2 < b+d) || (l-d < x2 && x2 < r+d && t-d < y1 && y1 < b+d) || (l-d < x2 && x2 < r+d && t-d < y2 && y2 < b+d))) {
				if(inst.snapElements[i].snapping) (inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
				inst.snapElements[i].snapping = false;
				continue;
			}

			if(o.snapMode != 'inner') {
				var ts = Math.abs(t - y2) <= d;
				var bs = Math.abs(b - y1) <= d;
				var ls = Math.abs(l - x2) <= d;
				var rs = Math.abs(r - x1) <= d;
				if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
				if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top;
				if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left;
				if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left;
			}

			var first = (ts || bs || ls || rs);

			if(o.snapMode != 'outer') {
				var ts = Math.abs(t - y1) <= d;
				var bs = Math.abs(b - y2) <= d;
				var ls = Math.abs(l - x1) <= d;
				var rs = Math.abs(r - x2) <= d;
				if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top;
				if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
				if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left;
				if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left;
			}

			if(!inst.snapElements[i].snapping && (ts || bs || ls || rs || first))
				(inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
			inst.snapElements[i].snapping = (ts || bs || ls || rs || first);

		};

	}
});

$.ui.plugin.add("draggable", "stack", {
	start: function(event, ui) {

		var o = $(this).data("draggable").options;

		var group = $.makeArray($(o.stack)).sort(function(a,b) {
			return (parseInt($(a).css("zIndex"),10) || 0) - (parseInt($(b).css("zIndex"),10) || 0);
		});
		if (!group.length) { return; }
		
		var min = parseInt(group[0].style.zIndex) || 0;
		$(group).each(function(i) {
			this.style.zIndex = min + i;
		});

		this[0].style.zIndex = min + group.length;

	}
});

$.ui.plugin.add("draggable", "zIndex", {
	start: function(event, ui) {
		var t = $(ui.helper), o = $(this).data("draggable").options;
		if(t.css("zIndex")) o._zIndex = t.css("zIndex");
		t.css('zIndex', o.zIndex);
	},
	stop: function(event, ui) {
		var o = $(this).data("draggable").options;
		if(o._zIndex) $(ui.helper).css('zIndex', o._zIndex);
	}
});

})(jQuery);

(function( $, undefined ) {

$.widget("ui.droppable", {
	widgetEventPrefix: "drop",
	options: {
		accept: '*',
		activeClass: false,
		addClasses: true,
		greedy: false,
		hoverClass: false,
		scope: 'default',
		tolerance: 'intersect'
	},
	_create: function() {

		var o = this.options, accept = o.accept;
		this.isover = 0; this.isout = 1;

		this.accept = $.isFunction(accept) ? accept : function(d) {
			return d.is(accept);
		};

		//Store the droppable's proportions
		this.proportions = { width: this.element[0].offsetWidth, height: this.element[0].offsetHeight };

		// Add the reference and positions to the manager
		$.ui.ddmanager.droppables[o.scope] = $.ui.ddmanager.droppables[o.scope] || [];
		$.ui.ddmanager.droppables[o.scope].push(this);

		(o.addClasses && this.element.addClass("ui-droppable"));

	},

	destroy: function() {
		var drop = $.ui.ddmanager.droppables[this.options.scope];
		for ( var i = 0; i < drop.length; i++ )
			if ( drop[i] == this )
				drop.splice(i, 1);

		this.element
			.removeClass("ui-droppable ui-droppable-disabled")
			.removeData("droppable")
			.unbind(".droppable");

		return this;
	},

	_setOption: function(key, value) {

		if(key == 'accept') {
			this.accept = $.isFunction(value) ? value : function(d) {
				return d.is(value);
			};
		}
		$.Widget.prototype._setOption.apply(this, arguments);
	},

	_activate: function(event) {
		var draggable = $.ui.ddmanager.current;
		if(this.options.activeClass) this.element.addClass(this.options.activeClass);
		(draggable && this._trigger('activate', event, this.ui(draggable)));
	},

	_deactivate: function(event) {
		var draggable = $.ui.ddmanager.current;
		if(this.options.activeClass) this.element.removeClass(this.options.activeClass);
		(draggable && this._trigger('deactivate', event, this.ui(draggable)));
	},

	_over: function(event) {

		var draggable = $.ui.ddmanager.current;
		if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element

		if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
			if(this.options.hoverClass) this.element.addClass(this.options.hoverClass);
			this._trigger('over', event, this.ui(draggable));
		}

	},

	_out: function(event) {

		var draggable = $.ui.ddmanager.current;
		if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element

		if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
			if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass);
			this._trigger('out', event, this.ui(draggable));
		}

	},

	_drop: function(event,custom) {

		var draggable = custom || $.ui.ddmanager.current;
		if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return false; // Bail if draggable and droppable are same element

		var childrenIntersection = false;
		this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function() {
			var inst = $.data(this, 'droppable');
			if(
				inst.options.greedy
				&& !inst.options.disabled
				&& inst.options.scope == draggable.options.scope
				&& inst.accept.call(inst.element[0], (draggable.currentItem || draggable.element))
				&& $.ui.intersect(draggable, $.extend(inst, { offset: inst.element.offset() }), inst.options.tolerance)
			) { childrenIntersection = true; return false; }
		});
		if(childrenIntersection) return false;

		if(this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
			if(this.options.activeClass) this.element.removeClass(this.options.activeClass);
			if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass);
			this._trigger('drop', event, this.ui(draggable));
			return this.element;
		}

		return false;

	},

	ui: function(c) {
		return {
			draggable: (c.currentItem || c.element),
			helper: c.helper,
			position: c.position,
			offset: c.positionAbs
		};
	}

});

$.extend($.ui.droppable, {
	version: "1.8.22"
});

$.ui.intersect = function(draggable, droppable, toleranceMode) {

	if (!droppable.offset) return false;

	var x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width,
		y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height;
	var l = droppable.offset.left, r = l + droppable.proportions.width,
		t = droppable.offset.top, b = t + droppable.proportions.height;

	switch (toleranceMode) {
		case 'fit':
			return (l <= x1 && x2 <= r
				&& t <= y1 && y2 <= b);
			break;
		case 'intersect':
			return (l < x1 + (draggable.helperProportions.width / 2) // Right Half
				&& x2 - (draggable.helperProportions.width / 2) < r // Left Half
				&& t < y1 + (draggable.helperProportions.height / 2) // Bottom Half
				&& y2 - (draggable.helperProportions.height / 2) < b ); // Top Half
			break;
		case 'pointer':
			var draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left),
				draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top),
				isOver = $.ui.isOver(draggableTop, draggableLeft, t, l, droppable.proportions.height, droppable.proportions.width);
			return isOver;
			break;
		case 'touch':
			return (
					(y1 >= t && y1 <= b) ||	// Top edge touching
					(y2 >= t && y2 <= b) ||	// Bottom edge touching
					(y1 < t && y2 > b)		// Surrounded vertically
				) && (
					(x1 >= l && x1 <= r) ||	// Left edge touching
					(x2 >= l && x2 <= r) ||	// Right edge touching
					(x1 < l && x2 > r)		// Surrounded horizontally
				);
			break;
		default:
			return false;
			break;
		}

};

/*
	This manager tracks offsets of draggables and droppables
*/
$.ui.ddmanager = {
	current: null,
	droppables: { 'default': [] },
	prepareOffsets: function(t, event) {

		var m = $.ui.ddmanager.droppables[t.options.scope] || [];
		var type = event ? event.type : null; // workaround for #2317
		var list = (t.currentItem || t.element).find(":data(droppable)").andSelf();

		droppablesLoop: for (var i = 0; i < m.length; i++) {

			if(m[i].options.disabled || (t && !m[i].accept.call(m[i].element[0],(t.currentItem || t.element)))) continue;	//No disabled and non-accepted
			for (var j=0; j < list.length; j++) { if(list[j] == m[i].element[0]) { m[i].proportions.height = 0; continue droppablesLoop; } }; //Filter out elements in the current dragged item
			m[i].visible = m[i].element.css("display") != "none"; if(!m[i].visible) continue; 									//If the element is not visible, continue

			if(type == "mousedown") m[i]._activate.call(m[i], event); //Activate the droppable if used directly from draggables

			m[i].offset = m[i].element.offset();
			m[i].proportions = { width: m[i].element[0].offsetWidth, height: m[i].element[0].offsetHeight };

		}

	},
	drop: function(draggable, event) {

		var dropped = false;
		$.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() {

			if(!this.options) return;
			if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance))
				dropped = this._drop.call(this, event) || dropped;

			if (!this.options.disabled && this.visible && this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
				this.isout = 1; this.isover = 0;
				this._deactivate.call(this, event);
			}

		});
		return dropped;

	},
	dragStart: function( draggable, event ) {
		//Listen for scrolling so that if the dragging causes scrolling the position of the droppables can be recalculated (see #5003)
		draggable.element.parents( ":not(body,html)" ).bind( "scroll.droppable", function() {
			if( !draggable.options.refreshPositions ) $.ui.ddmanager.prepareOffsets( draggable, event );
		});
	},
	drag: function(draggable, event) {

		//If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse.
		if(draggable.options.refreshPositions) $.ui.ddmanager.prepareOffsets(draggable, event);

		//Run through all droppables and check their positions based on specific tolerance options
		$.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() {

			if(this.options.disabled || this.greedyChild || !this.visible) return;
			var intersects = $.ui.intersect(draggable, this, this.options.tolerance);

			var c = !intersects && this.isover == 1 ? 'isout' : (intersects && this.isover == 0 ? 'isover' : null);
			if(!c) return;

			var parentInstance;
			if (this.options.greedy) {
				var parent = this.element.parents(':data(droppable):eq(0)');
				if (parent.length) {
					parentInstance = $.data(parent[0], 'droppable');
					parentInstance.greedyChild = (c == 'isover' ? 1 : 0);
				}
			}

			// we just moved into a greedy child
			if (parentInstance && c == 'isover') {
				parentInstance['isover'] = 0;
				parentInstance['isout'] = 1;
				parentInstance._out.call(parentInstance, event);
			}

			this[c] = 1; this[c == 'isout' ? 'isover' : 'isout'] = 0;
			this[c == "isover" ? "_over" : "_out"].call(this, event);

			// we just moved out of a greedy child
			if (parentInstance && c == 'isout') {
				parentInstance['isout'] = 0;
				parentInstance['isover'] = 1;
				parentInstance._over.call(parentInstance, event);
			}
		});

	},
	dragStop: function( draggable, event ) {
		draggable.element.parents( ":not(body,html)" ).unbind( "scroll.droppable" );
		//Call prepareOffsets one final time since IE does not fire return scroll events when overflow was caused by drag (see #5003)
		if( !draggable.options.refreshPositions ) $.ui.ddmanager.prepareOffsets( draggable, event );
	}
};

})(jQuery);

(function( $, undefined ) {

$.widget("ui.resizable", $.ui.mouse, {
	widgetEventPrefix: "resize",
	options: {
		alsoResize: false,
		animate: false,
		animateDuration: "slow",
		animateEasing: "swing",
		aspectRatio: false,
		autoHide: false,
		containment: false,
		ghost: false,
		grid: false,
		handles: "e,s,se",
		helper: false,
		maxHeight: null,
		maxWidth: null,
		minHeight: 10,
		minWidth: 10,
		zIndex: 1000
	},
	_create: function() {

		var self = this, o = this.options;
		this.element.addClass("ui-resizable");

		$.extend(this, {
			_aspectRatio: !!(o.aspectRatio),
			aspectRatio: o.aspectRatio,
			originalElement: this.element,
			_proportionallyResizeElements: [],
			_helper: o.helper || o.ghost || o.animate ? o.helper || 'ui-resizable-helper' : null
		});

		//Wrap the element if it cannot hold child nodes
		if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)) {

			//Create a wrapper element and set the wrapper to the new current internal element
			this.element.wrap(
				$('<div class="ui-wrapper" style="overflow: hidden;"></div>').css({
					position: this.element.css('position'),
					width: this.element.outerWidth(),
					height: this.element.outerHeight(),
					top: this.element.css('top'),
					left: this.element.css('left')
				})
			);

			//Overwrite the original this.element
			this.element = this.element.parent().data(
				"resizable", this.element.data('resizable')
			);

			this.elementIsWrapper = true;

			//Move margins to the wrapper
			this.element.css({ marginLeft: this.originalElement.css("marginLeft"), marginTop: this.originalElement.css("marginTop"), marginRight: this.originalElement.css("marginRight"), marginBottom: this.originalElement.css("marginBottom") });
			this.originalElement.css({ marginLeft: 0, marginTop: 0, marginRight: 0, marginBottom: 0});

			//Prevent Safari textarea resize
			this.originalResizeStyle = this.originalElement.css('resize');
			this.originalElement.css('resize', 'none');

			//Push the actual element to our proportionallyResize internal array
			this._proportionallyResizeElements.push(this.originalElement.css({ position: 'static', zoom: 1, display: 'block' }));

			// avoid IE jump (hard set the margin)
			this.originalElement.css({ margin: this.originalElement.css('margin') });

			// fix handlers offset
			this._proportionallyResize();

		}

		this.handles = o.handles || (!$('.ui-resizable-handle', this.element).length ? "e,s,se" : { n: '.ui-resizable-n', e: '.ui-resizable-e', s: '.ui-resizable-s', w: '.ui-resizable-w', se: '.ui-resizable-se', sw: '.ui-resizable-sw', ne: '.ui-resizable-ne', nw: '.ui-resizable-nw' });
		if(this.handles.constructor == String) {

			if(this.handles == 'all') this.handles = 'n,e,s,w,se,sw,ne,nw';
			var n = this.handles.split(","); this.handles = {};

			for(var i = 0; i < n.length; i++) {

				var handle = $.trim(n[i]), hname = 'ui-resizable-'+handle;
				var axis = $('<div class="ui-resizable-handle ' + hname + '"></div>');

				// Apply zIndex to all handles - see #7960
				axis.css({ zIndex: o.zIndex });

				//TODO : What's going on here?
				if ('se' == handle) {
					axis.addClass('ui-icon ui-icon-gripsmall-diagonal-se');
				};

				//Insert into internal handles object and append to element
				this.handles[handle] = '.ui-resizable-'+handle;
				this.element.append(axis);
			}

		}

		this._renderAxis = function(target) {

			target = target || this.element;

			for(var i in this.handles) {

				if(this.handles[i].constructor == String)
					this.handles[i] = $(this.handles[i], this.element).show();

				//Apply pad to wrapper element, needed to fix axis position (textarea, inputs, scrolls)
				if (this.elementIsWrapper && this.originalElement[0].nodeName.match(/textarea|input|select|button/i)) {

					var axis = $(this.handles[i], this.element), padWrapper = 0;

					//Checking the correct pad and border
					padWrapper = /sw|ne|nw|se|n|s/.test(i) ? axis.outerHeight() : axis.outerWidth();

					//The padding type i have to apply...
					var padPos = [ 'padding',
						/ne|nw|n/.test(i) ? 'Top' :
						/se|sw|s/.test(i) ? 'Bottom' :
						/^e$/.test(i) ? 'Right' : 'Left' ].join("");

					target.css(padPos, padWrapper);

					this._proportionallyResize();

				}

				//TODO: What's that good for? There's not anything to be executed left
				if(!$(this.handles[i]).length)
					continue;

			}
		};

		//TODO: make renderAxis a prototype function
		this._renderAxis(this.element);

		this._handles = $('.ui-resizable-handle', this.element)
			.disableSelection();

		//Matching axis name
		this._handles.mouseover(function() {
			if (!self.resizing) {
				if (this.className)
					var axis = this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);
				//Axis, default = se
				self.axis = axis && axis[1] ? axis[1] : 'se';
			}
		});

		//If we want to auto hide the elements
		if (o.autoHide) {
			this._handles.hide();
			$(this.element)
				.addClass("ui-resizable-autohide")
				.hover(function() {
					if (o.disabled) return;
					$(this).removeClass("ui-resizable-autohide");
					self._handles.show();
				},
				function(){
					if (o.disabled) return;
					if (!self.resizing) {
						$(this).addClass("ui-resizable-autohide");
						self._handles.hide();
					}
				});
		}

		//Initialize the mouse interaction
		this._mouseInit();

	},

	destroy: function() {

		this._mouseDestroy();

		var _destroy = function(exp) {
			$(exp).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing")
				.removeData("resizable").unbind(".resizable").find('.ui-resizable-handle').remove();
		};

		//TODO: Unwrap at same DOM position
		if (this.elementIsWrapper) {
			_destroy(this.element);
			var wrapper = this.element;
			wrapper.after(
				this.originalElement.css({
					position: wrapper.css('position'),
					width: wrapper.outerWidth(),
					height: wrapper.outerHeight(),
					top: wrapper.css('top'),
					left: wrapper.css('left')
				})
			).remove();
		}

		this.originalElement.css('resize', this.originalResizeStyle);
		_destroy(this.originalElement);

		return this;
	},

	_mouseCapture: function(event) {
		var handle = false;
		for (var i in this.handles) {
			if ($(this.handles[i])[0] == event.target) {
				handle = true;
			}
		}

		return !this.options.disabled && handle;
	},

	_mouseStart: function(event) {

		var o = this.options, iniPos = this.element.position(), el = this.element;

		this.resizing = true;
		this.documentScroll = { top: $(document).scrollTop(), left: $(document).scrollLeft() };

		// bugfix for http://dev.jquery.com/ticket/1749
		if (el.is('.ui-draggable') || (/absolute/).test(el.css('position'))) {
			el.css({ position: 'absolute', top: iniPos.top, left: iniPos.left });
		}

		this._renderProxy();

		var curleft = num(this.helper.css('left')), curtop = num(this.helper.css('top'));

		if (o.containment) {
			curleft += $(o.containment).scrollLeft() || 0;
			curtop += $(o.containment).scrollTop() || 0;
		}

		//Store needed variables
		this.offset = this.helper.offset();
		this.position = { left: curleft, top: curtop };
		this.size = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() };
		this.originalSize = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() };
		this.originalPosition = { left: curleft, top: curtop };
		this.sizeDiff = { width: el.outerWidth() - el.width(), height: el.outerHeight() - el.height() };
		this.originalMousePosition = { left: event.pageX, top: event.pageY };

		//Aspect Ratio
		this.aspectRatio = (typeof o.aspectRatio == 'number') ? o.aspectRatio : ((this.originalSize.width / this.originalSize.height) || 1);

	    var cursor = $('.ui-resizable-' + this.axis).css('cursor');
	    $('body').css('cursor', cursor == 'auto' ? this.axis + '-resize' : cursor);

		el.addClass("ui-resizable-resizing");
		this._propagate("start", event);
		return true;
	},

	_mouseDrag: function(event) {

		//Increase performance, avoid regex
		var el = this.helper, o = this.options, props = {},
			self = this, smp = this.originalMousePosition, a = this.axis;

		var dx = (event.pageX-smp.left)||0, dy = (event.pageY-smp.top)||0;
		var trigger = this._change[a];
		if (!trigger) return false;

		// Calculate the attrs that will be change
		var data = trigger.apply(this, [event, dx, dy]), ie6 = $.browser.msie && $.browser.version < 7, csdif = this.sizeDiff;

		// Put this in the mouseDrag handler since the user can start pressing shift while resizing
		this._updateVirtualBoundaries(event.shiftKey);
		if (this._aspectRatio || event.shiftKey)
			data = this._updateRatio(data, event);

		data = this._respectSize(data, event);

		// plugins callbacks need to be called first
		this._propagate("resize", event);

		el.css({
			top: this.position.top + "px", left: this.position.left + "px",
			width: this.size.width + "px", height: this.size.height + "px"
		});

		if (!this._helper && this._proportionallyResizeElements.length)
			this._proportionallyResize();

		this._updateCache(data);

		// calling the user callback at the end
		this._trigger('resize', event, this.ui());

		return false;
	},

	_mouseStop: function(event) {

		this.resizing = false;
		var o = this.options, self = this;

		if(this._helper) {
			var pr = this._proportionallyResizeElements, ista = pr.length && (/textarea/i).test(pr[0].nodeName),
				soffseth = ista && $.ui.hasScroll(pr[0], 'left') /* TODO - jump height */ ? 0 : self.sizeDiff.height,
				soffsetw = ista ? 0 : self.sizeDiff.width;

			var s = { width: (self.helper.width()  - soffsetw), height: (self.helper.height() - soffseth) },
				left = (parseInt(self.element.css('left'), 10) + (self.position.left - self.originalPosition.left)) || null,
				top = (parseInt(self.element.css('top'), 10) + (self.position.top - self.originalPosition.top)) || null;

			if (!o.animate)
				this.element.css($.extend(s, { top: top, left: left }));

			self.helper.height(self.size.height);
			self.helper.width(self.size.width);

			if (this._helper && !o.animate) this._proportionallyResize();
		}

		$('body').css('cursor', 'auto');

		this.element.removeClass("ui-resizable-resizing");

		this._propagate("stop", event);

		if (this._helper) this.helper.remove();
		return false;

	},

    _updateVirtualBoundaries: function(forceAspectRatio) {
        var o = this.options, pMinWidth, pMaxWidth, pMinHeight, pMaxHeight, b;

        b = {
            minWidth: isNumber(o.minWidth) ? o.minWidth : 0,
            maxWidth: isNumber(o.maxWidth) ? o.maxWidth : Infinity,
            minHeight: isNumber(o.minHeight) ? o.minHeight : 0,
            maxHeight: isNumber(o.maxHeight) ? o.maxHeight : Infinity
        };

        if(this._aspectRatio || forceAspectRatio) {
            // We want to create an enclosing box whose aspect ration is the requested one
            // First, compute the "projected" size for each dimension based on the aspect ratio and other dimension
            pMinWidth = b.minHeight * this.aspectRatio;
            pMinHeight = b.minWidth / this.aspectRatio;
            pMaxWidth = b.maxHeight * this.aspectRatio;
            pMaxHeight = b.maxWidth / this.aspectRatio;

            if(pMinWidth > b.minWidth) b.minWidth = pMinWidth;
            if(pMinHeight > b.minHeight) b.minHeight = pMinHeight;
            if(pMaxWidth < b.maxWidth) b.maxWidth = pMaxWidth;
            if(pMaxHeight < b.maxHeight) b.maxHeight = pMaxHeight;
        }
        this._vBoundaries = b;
    },

	_updateCache: function(data) {
		var o = this.options;
		this.offset = this.helper.offset();
		if (isNumber(data.left)) this.position.left = data.left;
		if (isNumber(data.top)) this.position.top = data.top;
		if (isNumber(data.height)) this.size.height = data.height;
		if (isNumber(data.width)) this.size.width = data.width;
	},

	_updateRatio: function(data, event) {

		var o = this.options, cpos = this.position, csize = this.size, a = this.axis;

		if (isNumber(data.height)) data.width = (data.height * this.aspectRatio);
		else if (isNumber(data.width)) data.height = (data.width / this.aspectRatio);

		if (a == 'sw') {
			data.left = cpos.left + (csize.width - data.width);
			data.top = null;
		}
		if (a == 'nw') {
			data.top = cpos.top + (csize.height - data.height);
			data.left = cpos.left + (csize.width - data.width);
		}

		return data;
	},

	_respectSize: function(data, event) {

		var el = this.helper, o = this._vBoundaries, pRatio = this._aspectRatio || event.shiftKey, a = this.axis,
				ismaxw = isNumber(data.width) && o.maxWidth && (o.maxWidth < data.width), ismaxh = isNumber(data.height) && o.maxHeight && (o.maxHeight < data.height),
					isminw = isNumber(data.width) && o.minWidth && (o.minWidth > data.width), isminh = isNumber(data.height) && o.minHeight && (o.minHeight > data.height);

		if (isminw) data.width = o.minWidth;
		if (isminh) data.height = o.minHeight;
		if (ismaxw) data.width = o.maxWidth;
		if (ismaxh) data.height = o.maxHeight;

		var dw = this.originalPosition.left + this.originalSize.width, dh = this.position.top + this.size.height;
		var cw = /sw|nw|w/.test(a), ch = /nw|ne|n/.test(a);

		if (isminw && cw) data.left = dw - o.minWidth;
		if (ismaxw && cw) data.left = dw - o.maxWidth;
		if (isminh && ch)	data.top = dh - o.minHeight;
		if (ismaxh && ch)	data.top = dh - o.maxHeight;

		// fixing jump error on top/left - bug #2330
		var isNotwh = !data.width && !data.height;
		if (isNotwh && !data.left && data.top) data.top = null;
		else if (isNotwh && !data.top && data.left) data.left = null;

		return data;
	},

	_proportionallyResize: function() {

		var o = this.options;
		if (!this._proportionallyResizeElements.length) return;
		var element = this.helper || this.element;

		for (var i=0; i < this._proportionallyResizeElements.length; i++) {

			var prel = this._proportionallyResizeElements[i];

			if (!this.borderDif) {
				var b = [prel.css('borderTopWidth'), prel.css('borderRightWidth'), prel.css('borderBottomWidth'), prel.css('borderLeftWidth')],
					p = [prel.css('paddingTop'), prel.css('paddingRight'), prel.css('paddingBottom'), prel.css('paddingLeft')];

				this.borderDif = $.map(b, function(v, i) {
					var border = parseInt(v,10)||0, padding = parseInt(p[i],10)||0;
					return border + padding;
				});
			}

			if ($.browser.msie && !(!($(element).is(':hidden') || $(element).parents(':hidden').length)))
				continue;

			prel.css({
				height: (element.height() - this.borderDif[0] - this.borderDif[2]) || 0,
				width: (element.width() - this.borderDif[1] - this.borderDif[3]) || 0
			});

		};

	},

	_renderProxy: function() {

		var el = this.element, o = this.options;
		this.elementOffset = el.offset();

		if(this._helper) {

			this.helper = this.helper || $('<div style="overflow:hidden;"></div>');

			// fix ie6 offset TODO: This seems broken
			var ie6 = $.browser.msie && $.browser.version < 7, ie6offset = (ie6 ? 1 : 0),
			pxyoffset = ( ie6 ? 2 : -1 );

			this.helper.addClass(this._helper).css({
				width: this.element.outerWidth() + pxyoffset,
				height: this.element.outerHeight() + pxyoffset,
				position: 'absolute',
				left: this.elementOffset.left - ie6offset +'px',
				top: this.elementOffset.top - ie6offset +'px',
				zIndex: ++o.zIndex //TODO: Don't modify option
			});

			this.helper
				.appendTo("body")
				.disableSelection();

		} else {
			this.helper = this.element;
		}

	},

	_change: {
		e: function(event, dx, dy) {
			return { width: this.originalSize.width + dx };
		},
		w: function(event, dx, dy) {
			var o = this.options, cs = this.originalSize, sp = this.originalPosition;
			return { left: sp.left + dx, width: cs.width - dx };
		},
		n: function(event, dx, dy) {
			var o = this.options, cs = this.originalSize, sp = this.originalPosition;
			return { top: sp.top + dy, height: cs.height - dy };
		},
		s: function(event, dx, dy) {
			return { height: this.originalSize.height + dy };
		},
		se: function(event, dx, dy) {
			return $.extend(this._change.s.apply(this, arguments), this._change.e.apply(this, [event, dx, dy]));
		},
		sw: function(event, dx, dy) {
			return $.extend(this._change.s.apply(this, arguments), this._change.w.apply(this, [event, dx, dy]));
		},
		ne: function(event, dx, dy) {
			return $.extend(this._change.n.apply(this, arguments), this._change.e.apply(this, [event, dx, dy]));
		},
		nw: function(event, dx, dy) {
			return $.extend(this._change.n.apply(this, arguments), this._change.w.apply(this, [event, dx, dy]));
		}
	},

	_propagate: function(n, event) {
		$.ui.plugin.call(this, n, [event, this.ui()]);
		(n != "resize" && this._trigger(n, event, this.ui()));
	},

	plugins: {},

	ui: function() {
		return {
			originalElement: this.originalElement,
			element: this.element,
			helper: this.helper,
			position: this.position,
			size: this.size,
			originalSize: this.originalSize,
			originalPosition: this.originalPosition
		};
	}

});

$.extend($.ui.resizable, {
	version: "1.8.22"
});

/*
 * Resizable Extensions
 */

$.ui.plugin.add("resizable", "alsoResize", {

	start: function (event, ui) {
		var self = $(this).data("resizable"), o = self.options;

		var _store = function (exp) {
			$(exp).each(function() {
				var el = $(this);
				el.data("resizable-alsoresize", {
					width: parseInt(el.width(), 10), height: parseInt(el.height(), 10),
					left: parseInt(el.css('left'), 10), top: parseInt(el.css('top'), 10)
				});
			});
		};

		if (typeof(o.alsoResize) == 'object' && !o.alsoResize.parentNode) {
			if (o.alsoResize.length) { o.alsoResize = o.alsoResize[0]; _store(o.alsoResize); }
			else { $.each(o.alsoResize, function (exp) { _store(exp); }); }
		}else{
			_store(o.alsoResize);
		}
	},

	resize: function (event, ui) {
		var self = $(this).data("resizable"), o = self.options, os = self.originalSize, op = self.originalPosition;

		var delta = {
			height: (self.size.height - os.height) || 0, width: (self.size.width - os.width) || 0,
			top: (self.position.top - op.top) || 0, left: (self.position.left - op.left) || 0
		},

		_alsoResize = function (exp, c) {
			$(exp).each(function() {
				var el = $(this), start = $(this).data("resizable-alsoresize"), style = {}, 
					css = c && c.length ? c : el.parents(ui.originalElement[0]).length ? ['width', 'height'] : ['width', 'height', 'top', 'left'];

				$.each(css, function (i, prop) {
					var sum = (start[prop]||0) + (delta[prop]||0);
					if (sum && sum >= 0)
						style[prop] = sum || null;
				});

				el.css(style);
			});
		};

		if (typeof(o.alsoResize) == 'object' && !o.alsoResize.nodeType) {
			$.each(o.alsoResize, function (exp, c) { _alsoResize(exp, c); });
		}else{
			_alsoResize(o.alsoResize);
		}
	},

	stop: function (event, ui) {
		$(this).removeData("resizable-alsoresize");
	}
});

$.ui.plugin.add("resizable", "animate", {

	stop: function(event, ui) {
		var self = $(this).data("resizable"), o = self.options;

		var pr = self._proportionallyResizeElements, ista = pr.length && (/textarea/i).test(pr[0].nodeName),
					soffseth = ista && $.ui.hasScroll(pr[0], 'left') /* TODO - jump height */ ? 0 : self.sizeDiff.height,
						soffsetw = ista ? 0 : self.sizeDiff.width;

		var style = { width: (self.size.width - soffsetw), height: (self.size.height - soffseth) },
					left = (parseInt(self.element.css('left'), 10) + (self.position.left - self.originalPosition.left)) || null,
						top = (parseInt(self.element.css('top'), 10) + (self.position.top - self.originalPosition.top)) || null;

		self.element.animate(
			$.extend(style, top && left ? { top: top, left: left } : {}), {
				duration: o.animateDuration,
				easing: o.animateEasing,
				step: function() {

					var data = {
						width: parseInt(self.element.css('width'), 10),
						height: parseInt(self.element.css('height'), 10),
						top: parseInt(self.element.css('top'), 10),
						left: parseInt(self.element.css('left'), 10)
					};

					if (pr && pr.length) $(pr[0]).css({ width: data.width, height: data.height });

					// propagating resize, and updating values for each animation step
					self._updateCache(data);
					self._propagate("resize", event);

				}
			}
		);
	}

});

$.ui.plugin.add("resizable", "containment", {

	start: function(event, ui) {
		var self = $(this).data("resizable"), o = self.options, el = self.element;
		var oc = o.containment,	ce = (oc instanceof $) ? oc.get(0) : (/parent/.test(oc)) ? el.parent().get(0) : oc;
		if (!ce) return;

		self.containerElement = $(ce);

		if (/document/.test(oc) || oc == document) {
			self.containerOffset = { left: 0, top: 0 };
			self.containerPosition = { left: 0, top: 0 };

			self.parentData = {
				element: $(document), left: 0, top: 0,
				width: $(document).width(), height: $(document).height() || document.body.parentNode.scrollHeight
			};
		}

		// i'm a node, so compute top, left, right, bottom
		else {
			var element = $(ce), p = [];
			$([ "Top", "Right", "Left", "Bottom" ]).each(function(i, name) { p[i] = num(element.css("padding" + name)); });

			self.containerOffset = element.offset();
			self.containerPosition = element.position();
			self.containerSize = { height: (element.innerHeight() - p[3]), width: (element.innerWidth() - p[1]) };

			var co = self.containerOffset, ch = self.containerSize.height,	cw = self.containerSize.width,
						width = ($.ui.hasScroll(ce, "left") ? ce.scrollWidth : cw ), height = ($.ui.hasScroll(ce) ? ce.scrollHeight : ch);

			self.parentData = {
				element: ce, left: co.left, top: co.top, width: width, height: height
			};
		}
	},

	resize: function(event, ui) {
		var self = $(this).data("resizable"), o = self.options,
				ps = self.containerSize, co = self.containerOffset, cs = self.size, cp = self.position,
				pRatio = self._aspectRatio || event.shiftKey, cop = { top:0, left:0 }, ce = self.containerElement;

		if (ce[0] != document && (/static/).test(ce.css('position'))) cop = co;

		if (cp.left < (self._helper ? co.left : 0)) {
			self.size.width = self.size.width + (self._helper ? (self.position.left - co.left) : (self.position.left - cop.left));
			if (pRatio) self.size.height = self.size.width / self.aspectRatio;
			self.position.left = o.helper ? co.left : 0;
		}

		if (cp.top < (self._helper ? co.top : 0)) {
			self.size.height = self.size.height + (self._helper ? (self.position.top - co.top) : self.position.top);
			if (pRatio) self.size.width = self.size.height * self.aspectRatio;
			self.position.top = self._helper ? co.top : 0;
		}

		self.offset.left = self.parentData.left+self.position.left;
		self.offset.top = self.parentData.top+self.position.top;

		var woset = Math.abs( (self._helper ? self.offset.left - cop.left : (self.offset.left - cop.left)) + self.sizeDiff.width ),
					hoset = Math.abs( (self._helper ? self.offset.top - cop.top : (self.offset.top - co.top)) + self.sizeDiff.height );

		var isParent = self.containerElement.get(0) == self.element.parent().get(0),
		    isOffsetRelative = /relative|absolute/.test(self.containerElement.css('position'));

		if(isParent && isOffsetRelative) woset -= self.parentData.left;

		if (woset + self.size.width >= self.parentData.width) {
			self.size.width = self.parentData.width - woset;
			if (pRatio) self.size.height = self.size.width / self.aspectRatio;
		}

		if (hoset + self.size.height >= self.parentData.height) {
			self.size.height = self.parentData.height - hoset;
			if (pRatio) self.size.width = self.size.height * self.aspectRatio;
		}
	},

	stop: function(event, ui){
		var self = $(this).data("resizable"), o = self.options, cp = self.position,
				co = self.containerOffset, cop = self.containerPosition, ce = self.containerElement;

		var helper = $(self.helper), ho = helper.offset(), w = helper.outerWidth() - self.sizeDiff.width, h = helper.outerHeight() - self.sizeDiff.height;

		if (self._helper && !o.animate && (/relative/).test(ce.css('position')))
			$(this).css({ left: ho.left - cop.left - co.left, width: w, height: h });

		if (self._helper && !o.animate && (/static/).test(ce.css('position')))
			$(this).css({ left: ho.left - cop.left - co.left, width: w, height: h });

	}
});

$.ui.plugin.add("resizable", "ghost", {

	start: function(event, ui) {

		var self = $(this).data("resizable"), o = self.options, cs = self.size;

		self.ghost = self.originalElement.clone();
		self.ghost
			.css({ opacity: .25, display: 'block', position: 'relative', height: cs.height, width: cs.width, margin: 0, left: 0, top: 0 })
			.addClass('ui-resizable-ghost')
			.addClass(typeof o.ghost == 'string' ? o.ghost : '');

		self.ghost.appendTo(self.helper);

	},

	resize: function(event, ui){
		var self = $(this).data("resizable"), o = self.options;
		if (self.ghost) self.ghost.css({ position: 'relative', height: self.size.height, width: self.size.width });
	},

	stop: function(event, ui){
		var self = $(this).data("resizable"), o = self.options;
		if (self.ghost && self.helper) self.helper.get(0).removeChild(self.ghost.get(0));
	}

});

$.ui.plugin.add("resizable", "grid", {

	resize: function(event, ui) {
		var self = $(this).data("resizable"), o = self.options, cs = self.size, os = self.originalSize, op = self.originalPosition, a = self.axis, ratio = o._aspectRatio || event.shiftKey;
		o.grid = typeof o.grid == "number" ? [o.grid, o.grid] : o.grid;
		var ox = Math.round((cs.width - os.width) / (o.grid[0]||1)) * (o.grid[0]||1), oy = Math.round((cs.height - os.height) / (o.grid[1]||1)) * (o.grid[1]||1);

		if (/^(se|s|e)$/.test(a)) {
			self.size.width = os.width + ox;
			self.size.height = os.height + oy;
		}
		else if (/^(ne)$/.test(a)) {
			self.size.width = os.width + ox;
			self.size.height = os.height + oy;
			self.position.top = op.top - oy;
		}
		else if (/^(sw)$/.test(a)) {
			self.size.width = os.width + ox;
			self.size.height = os.height + oy;
			self.position.left = op.left - ox;
		}
		else {
			self.size.width = os.width + ox;
			self.size.height = os.height + oy;
			self.position.top = op.top - oy;
			self.position.left = op.left - ox;
		}
	}

});

var num = function(v) {
	return parseInt(v, 10) || 0;
};

var isNumber = function(value) {
	return !isNaN(parseInt(value, 10));
};

})(jQuery);

(function( $, undefined ) {

$.widget("ui.selectable", $.ui.mouse, {
	options: {
		appendTo: 'body',
		autoRefresh: true,
		distance: 0,
		filter: '*',
		tolerance: 'touch'
	},
	_create: function() {
		var self = this;

		this.element.addClass("ui-selectable");

		this.dragged = false;

		// cache selectee children based on filter
		var selectees;
		this.refresh = function() {
			selectees = $(self.options.filter, self.element[0]);
			selectees.addClass("ui-selectee");
			selectees.each(function() {
				var $this = $(this);
				var pos = $this.offset();
				$.data(this, "selectable-item", {
					element: this,
					$element: $this,
					left: pos.left,
					top: pos.top,
					right: pos.left + $this.outerWidth(),
					bottom: pos.top + $this.outerHeight(),
					startselected: false,
					selected: $this.hasClass('ui-selected'),
					selecting: $this.hasClass('ui-selecting'),
					unselecting: $this.hasClass('ui-unselecting')
				});
			});
		};
		this.refresh();

		this.selectees = selectees.addClass("ui-selectee");

		this._mouseInit();

		this.helper = $("<div class='ui-selectable-helper'></div>");
	},

	destroy: function() {
		this.selectees
			.removeClass("ui-selectee")
			.removeData("selectable-item");
		this.element
			.removeClass("ui-selectable ui-selectable-disabled")
			.removeData("selectable")
			.unbind(".selectable");
		this._mouseDestroy();

		return this;
	},

	_mouseStart: function(event) {
		var self = this;

		this.opos = [event.pageX, event.pageY];

		if (this.options.disabled)
			return;

		var options = this.options;

		this.selectees = $(options.filter, this.element[0]);

		this._trigger("start", event);

		$(options.appendTo).append(this.helper);
		// position helper (lasso)
		this.helper.css({
			"left": event.clientX,
			"top": event.clientY,
			"width": 0,
			"height": 0
		});

		if (options.autoRefresh) {
			this.refresh();
		}

		this.selectees.filter('.ui-selected').each(function() {
			var selectee = $.data(this, "selectable-item");
			selectee.startselected = true;
			if (!event.metaKey && !event.ctrlKey) {
				selectee.$element.removeClass('ui-selected');
				selectee.selected = false;
				selectee.$element.addClass('ui-unselecting');
				selectee.unselecting = true;
				// selectable UNSELECTING callback
				self._trigger("unselecting", event, {
					unselecting: selectee.element
				});
			}
		});

		$(event.target).parents().andSelf().each(function() {
			var selectee = $.data(this, "selectable-item");
			if (selectee) {
				var doSelect = (!event.metaKey && !event.ctrlKey) || !selectee.$element.hasClass('ui-selected');
				selectee.$element
					.removeClass(doSelect ? "ui-unselecting" : "ui-selected")
					.addClass(doSelect ? "ui-selecting" : "ui-unselecting");
				selectee.unselecting = !doSelect;
				selectee.selecting = doSelect;
				selectee.selected = doSelect;
				// selectable (UN)SELECTING callback
				if (doSelect) {
					self._trigger("selecting", event, {
						selecting: selectee.element
					});
				} else {
					self._trigger("unselecting", event, {
						unselecting: selectee.element
					});
				}
				return false;
			}
		});

	},

	_mouseDrag: function(event) {
		var self = this;
		this.dragged = true;

		if (this.options.disabled)
			return;

		var options = this.options;

		var x1 = this.opos[0], y1 = this.opos[1], x2 = event.pageX, y2 = event.pageY;
		if (x1 > x2) { var tmp = x2; x2 = x1; x1 = tmp; }
		if (y1 > y2) { var tmp = y2; y2 = y1; y1 = tmp; }
		this.helper.css({left: x1, top: y1, width: x2-x1, height: y2-y1});

		this.selectees.each(function() {
			var selectee = $.data(this, "selectable-item");
			//prevent helper from being selected if appendTo: selectable
			if (!selectee || selectee.element == self.element[0])
				return;
			var hit = false;
			if (options.tolerance == 'touch') {
				hit = ( !(selectee.left > x2 || selectee.right < x1 || selectee.top > y2 || selectee.bottom < y1) );
			} else if (options.tolerance == 'fit') {
				hit = (selectee.left > x1 && selectee.right < x2 && selectee.top > y1 && selectee.bottom < y2);
			}

			if (hit) {
				// SELECT
				if (selectee.selected) {
					selectee.$element.removeClass('ui-selected');
					selectee.selected = false;
				}
				if (selectee.unselecting) {
					selectee.$element.removeClass('ui-unselecting');
					selectee.unselecting = false;
				}
				if (!selectee.selecting) {
					selectee.$element.addClass('ui-selecting');
					selectee.selecting = true;
					// selectable SELECTING callback
					self._trigger("selecting", event, {
						selecting: selectee.element
					});
				}
			} else {
				// UNSELECT
				if (selectee.selecting) {
					if ((event.metaKey || event.ctrlKey) && selectee.startselected) {
						selectee.$element.removeClass('ui-selecting');
						selectee.selecting = false;
						selectee.$element.addClass('ui-selected');
						selectee.selected = true;
					} else {
						selectee.$element.removeClass('ui-selecting');
						selectee.selecting = false;
						if (selectee.startselected) {
							selectee.$element.addClass('ui-unselecting');
							selectee.unselecting = true;
						}
						// selectable UNSELECTING callback
						self._trigger("unselecting", event, {
							unselecting: selectee.element
						});
					}
				}
				if (selectee.selected) {
					if (!event.metaKey && !event.ctrlKey && !selectee.startselected) {
						selectee.$element.removeClass('ui-selected');
						selectee.selected = false;

						selectee.$element.addClass('ui-unselecting');
						selectee.unselecting = true;
						// selectable UNSELECTING callback
						self._trigger("unselecting", event, {
							unselecting: selectee.element
						});
					}
				}
			}
		});

		return false;
	},

	_mouseStop: function(event) {
		var self = this;

		this.dragged = false;

		var options = this.options;

		$('.ui-unselecting', this.element[0]).each(function() {
			var selectee = $.data(this, "selectable-item");
			selectee.$element.removeClass('ui-unselecting');
			selectee.unselecting = false;
			selectee.startselected = false;
			self._trigger("unselected", event, {
				unselected: selectee.element
			});
		});
		$('.ui-selecting', this.element[0]).each(function() {
			var selectee = $.data(this, "selectable-item");
			selectee.$element.removeClass('ui-selecting').addClass('ui-selected');
			selectee.selecting = false;
			selectee.selected = true;
			selectee.startselected = true;
			self._trigger("selected", event, {
				selected: selectee.element
			});
		});
		this._trigger("stop", event);

		this.helper.remove();

		return false;
	}

});

$.extend($.ui.selectable, {
	version: "1.8.22"
});

})(jQuery);

(function( $, undefined ) {

$.widget("ui.sortable", $.ui.mouse, {
	widgetEventPrefix: "sort",
	ready: false,
	options: {
		appendTo: "parent",
		axis: false,
		connectWith: false,
		containment: false,
		cursor: 'auto',
		cursorAt: false,
		dropOnEmpty: true,
		forcePlaceholderSize: false,
		forceHelperSize: false,
		grid: false,
		handle: false,
		helper: "original",
		items: '> *',
		opacity: false,
		placeholder: false,
		revert: false,
		scroll: true,
		scrollSensitivity: 20,
		scrollSpeed: 20,
		scope: "default",
		tolerance: "intersect",
		zIndex: 1000
	},
	_create: function() {

		var o = this.options;
		this.containerCache = {};
		this.element.addClass("ui-sortable");

		//Get the items
		this.refresh();

		//Let's determine if the items are being displayed horizontally
		this.floating = this.items.length ? o.axis === 'x' || (/left|right/).test(this.items[0].item.css('float')) || (/inline|table-cell/).test(this.items[0].item.css('display')) : false;

		//Let's determine the parent's offset
		this.offset = this.element.offset();

		//Initialize mouse events for interaction
		this._mouseInit();
		
		//We're ready to go
		this.ready = true

	},

	destroy: function() {
		$.Widget.prototype.destroy.call( this );
		this.element
			.removeClass("ui-sortable ui-sortable-disabled");
		this._mouseDestroy();

		for ( var i = this.items.length - 1; i >= 0; i-- )
			this.items[i].item.removeData(this.widgetName + "-item");

		return this;
	},

	_setOption: function(key, value){
		if ( key === "disabled" ) {
			this.options[ key ] = value;
	
			this.widget()
				[ value ? "addClass" : "removeClass"]( "ui-sortable-disabled" );
		} else {
			// Don't call widget base _setOption for disable as it adds ui-state-disabled class
			$.Widget.prototype._setOption.apply(this, arguments);
		}
	},

	_mouseCapture: function(event, overrideHandle) {
		var that = this;

		if (this.reverting) {
			return false;
		}

		if(this.options.disabled || this.options.type == 'static') return false;

		//We have to refresh the items data once first
		this._refreshItems(event);

		//Find out if the clicked node (or one of its parents) is a actual item in this.items
		var currentItem = null, self = this, nodes = $(event.target).parents().each(function() {
			if($.data(this, that.widgetName + '-item') == self) {
				currentItem = $(this);
				return false;
			}
		});
		if($.data(event.target, that.widgetName + '-item') == self) currentItem = $(event.target);

		if(!currentItem) return false;
		if(this.options.handle && !overrideHandle) {
			var validHandle = false;

			$(this.options.handle, currentItem).find("*").andSelf().each(function() { if(this == event.target) validHandle = true; });
			if(!validHandle) return false;
		}

		this.currentItem = currentItem;
		this._removeCurrentsFromItems();
		return true;

	},

	_mouseStart: function(event, overrideHandle, noActivation) {

		var o = this.options, self = this;
		this.currentContainer = this;

		//We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture
		this.refreshPositions();

		//Create and append the visible helper
		this.helper = this._createHelper(event);

		//Cache the helper size
		this._cacheHelperProportions();

		/*
		 * - Position generation -
		 * This block generates everything position related - it's the core of draggables.
		 */

		//Cache the margins of the original element
		this._cacheMargins();

		//Get the next scrolling parent
		this.scrollParent = this.helper.scrollParent();

		//The element's absolute position on the page minus margins
		this.offset = this.currentItem.offset();
		this.offset = {
			top: this.offset.top - this.margins.top,
			left: this.offset.left - this.margins.left
		};

		$.extend(this.offset, {
			click: { //Where the click happened, relative to the element
				left: event.pageX - this.offset.left,
				top: event.pageY - this.offset.top
			},
			parent: this._getParentOffset(),
			relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
		});

		// Only after we got the offset, we can change the helper's position to absolute
		// TODO: Still need to figure out a way to make relative sorting possible
		this.helper.css("position", "absolute");
		this.cssPosition = this.helper.css("position");
		
		//Generate the original position
		this.originalPosition = this._generatePosition(event);
		this.originalPageX = event.pageX;
		this.originalPageY = event.pageY;

		//Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
		(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));

		//Cache the former DOM position
		this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] };

		//If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way
		if(this.helper[0] != this.currentItem[0]) {
			this.currentItem.hide();
		}

		//Create the placeholder
		this._createPlaceholder();

		//Set a containment if given in the options
		if(o.containment)
			this._setContainment();

		if(o.cursor) { // cursor option
			if ($('body').css("cursor")) this._storedCursor = $('body').css("cursor");
			$('body').css("cursor", o.cursor);
		}

		if(o.opacity) { // opacity option
			if (this.helper.css("opacity")) this._storedOpacity = this.helper.css("opacity");
			this.helper.css("opacity", o.opacity);
		}

		if(o.zIndex) { // zIndex option
			if (this.helper.css("zIndex")) this._storedZIndex = this.helper.css("zIndex");
			this.helper.css("zIndex", o.zIndex);
		}

		//Prepare scrolling
		if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML')
			this.overflowOffset = this.scrollParent.offset();

		//Call callbacks
		this._trigger("start", event, this._uiHash());

		//Recache the helper size
		if(!this._preserveHelperProportions)
			this._cacheHelperProportions();


		//Post 'activate' events to possible containers
		if(!noActivation) {
			 for (var i = this.containers.length - 1; i >= 0; i--) { this.containers[i]._trigger("activate", event, self._uiHash(this)); }
		}

		//Prepare possible droppables
		if($.ui.ddmanager)
			$.ui.ddmanager.current = this;

		if ($.ui.ddmanager && !o.dropBehaviour)
			$.ui.ddmanager.prepareOffsets(this, event);

		this.dragging = true;

		this.helper.addClass("ui-sortable-helper");
		this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position
		return true;

	},

	_mouseDrag: function(event) {

		//Compute the helpers position
		this.position = this._generatePosition(event);
		this.positionAbs = this._convertPositionTo("absolute");

		if (!this.lastPositionAbs) {
			this.lastPositionAbs = this.positionAbs;
		}

		//Do scrolling
		if(this.options.scroll) {
			var o = this.options, scrolled = false;
			if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') {

				if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
					this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
				else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity)
					this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;

				if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
					this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
				else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity)
					this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;

			} else {

				if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
					scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
				else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
					scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);

				if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
					scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
				else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
					scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);

			}

			if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
				$.ui.ddmanager.prepareOffsets(this, event);
		}

		//Regenerate the absolute position used for position checks
		this.positionAbs = this._convertPositionTo("absolute");

		//Set the helper position
		if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
		if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';

		//Rearrange
		for (var i = this.items.length - 1; i >= 0; i--) {

			//Cache variables and intersection, continue if no intersection
			var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item);
			if (!intersection) continue;

			if(itemElement != this.currentItem[0] //cannot intersect with itself
				&&	this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before
				&&	!$.ui.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked
				&& (this.options.type == 'semi-dynamic' ? !$.ui.contains(this.element[0], itemElement) : true)
				//&& itemElement.parentNode == this.placeholder[0].parentNode // only rearrange items within the same container
			) {

				this.direction = intersection == 1 ? "down" : "up";

				if (this.options.tolerance == "pointer" || this._intersectsWithSides(item)) {
					this._rearrange(event, item);
				} else {
					break;
				}

				this._trigger("change", event, this._uiHash());
				break;
			}
		}

		//Post events to containers
		this._contactContainers(event);

		//Interconnect with droppables
		if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);

		//Call callbacks
		this._trigger('sort', event, this._uiHash());

		this.lastPositionAbs = this.positionAbs;
		return false;

	},

	_mouseStop: function(event, noPropagation) {

		if(!event) return;

		//If we are using droppables, inform the manager about the drop
		if ($.ui.ddmanager && !this.options.dropBehaviour)
			$.ui.ddmanager.drop(this, event);

		if(this.options.revert) {
			var self = this;
			var cur = self.placeholder.offset();

			self.reverting = true;

			$(this.helper).animate({
				left: cur.left - this.offset.parent.left - self.margins.left + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollLeft),
				top: cur.top - this.offset.parent.top - self.margins.top + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollTop)
			}, parseInt(this.options.revert, 10) || 500, function() {
				self._clear(event);
			});
		} else {
			this._clear(event, noPropagation);
		}

		return false;

	},

	cancel: function() {

		var self = this;

		if(this.dragging) {

			this._mouseUp({ target: null });

			if(this.options.helper == "original")
				this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
			else
				this.currentItem.show();

			//Post deactivating events to containers
			for (var i = this.containers.length - 1; i >= 0; i--){
				this.containers[i]._trigger("deactivate", null, self._uiHash(this));
				if(this.containers[i].containerCache.over) {
					this.containers[i]._trigger("out", null, self._uiHash(this));
					this.containers[i].containerCache.over = 0;
				}
			}

		}

		if (this.placeholder) {
			//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
			if(this.placeholder[0].parentNode) this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
			if(this.options.helper != "original" && this.helper && this.helper[0].parentNode) this.helper.remove();

			$.extend(this, {
				helper: null,
				dragging: false,
				reverting: false,
				_noFinalSort: null
			});

			if(this.domPosition.prev) {
				$(this.domPosition.prev).after(this.currentItem);
			} else {
				$(this.domPosition.parent).prepend(this.currentItem);
			}
		}

		return this;

	},

	serialize: function(o) {

		var items = this._getItemsAsjQuery(o && o.connected);
		var str = []; o = o || {};

		$(items).each(function() {
			var res = ($(o.item || this).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
			if(res) str.push((o.key || res[1]+'[]')+'='+(o.key && o.expression ? res[1] : res[2]));
		});

		if(!str.length && o.key) {
			str.push(o.key + '=');
		}

		return str.join('&');

	},

	toArray: function(o) {

		var items = this._getItemsAsjQuery(o && o.connected);
		var ret = []; o = o || {};

		items.each(function() { ret.push($(o.item || this).attr(o.attribute || 'id') || ''); });
		return ret;

	},

	/* Be careful with the following core functions */
	_intersectsWith: function(item) {

		var x1 = this.positionAbs.left,
			x2 = x1 + this.helperProportions.width,
			y1 = this.positionAbs.top,
			y2 = y1 + this.helperProportions.height;

		var l = item.left,
			r = l + item.width,
			t = item.top,
			b = t + item.height;

		var dyClick = this.offset.click.top,
			dxClick = this.offset.click.left;

		var isOverElement = (y1 + dyClick) > t && (y1 + dyClick) < b && (x1 + dxClick) > l && (x1 + dxClick) < r;

		if(	   this.options.tolerance == "pointer"
			|| this.options.forcePointerForContainers
			|| (this.options.tolerance != "pointer" && this.helperProportions[this.floating ? 'width' : 'height'] > item[this.floating ? 'width' : 'height'])
		) {
			return isOverElement;
		} else {

			return (l < x1 + (this.helperProportions.width / 2) // Right Half
				&& x2 - (this.helperProportions.width / 2) < r // Left Half
				&& t < y1 + (this.helperProportions.height / 2) // Bottom Half
				&& y2 - (this.helperProportions.height / 2) < b ); // Top Half

		}
	},

	_intersectsWithPointer: function(item) {

		var isOverElementHeight = (this.options.axis === 'x') || $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height),
			isOverElementWidth = (this.options.axis === 'y') || $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width),
			isOverElement = isOverElementHeight && isOverElementWidth,
			verticalDirection = this._getDragVerticalDirection(),
			horizontalDirection = this._getDragHorizontalDirection();

		if (!isOverElement)
			return false;

		return this.floating ?
			( ((horizontalDirection && horizontalDirection == "right") || verticalDirection == "down") ? 2 : 1 )
			: ( verticalDirection && (verticalDirection == "down" ? 2 : 1) );

	},

	_intersectsWithSides: function(item) {

		var isOverBottomHalf = $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height),
			isOverRightHalf = $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width),
			verticalDirection = this._getDragVerticalDirection(),
			horizontalDirection = this._getDragHorizontalDirection();

		if (this.floating && horizontalDirection) {
			return ((horizontalDirection == "right" && isOverRightHalf) || (horizontalDirection == "left" && !isOverRightHalf));
		} else {
			return verticalDirection && ((verticalDirection == "down" && isOverBottomHalf) || (verticalDirection == "up" && !isOverBottomHalf));
		}

	},

	_getDragVerticalDirection: function() {
		var delta = this.positionAbs.top - this.lastPositionAbs.top;
		return delta != 0 && (delta > 0 ? "down" : "up");
	},

	_getDragHorizontalDirection: function() {
		var delta = this.positionAbs.left - this.lastPositionAbs.left;
		return delta != 0 && (delta > 0 ? "right" : "left");
	},

	refresh: function(event) {
		this._refreshItems(event);
		this.refreshPositions();
		return this;
	},

	_connectWith: function() {
		var options = this.options;
		return options.connectWith.constructor == String
			? [options.connectWith]
			: options.connectWith;
	},
	
	_getItemsAsjQuery: function(connected) {

		var self = this;
		var items = [];
		var queries = [];
		var connectWith = this._connectWith();

		if(connectWith && connected) {
			for (var i = connectWith.length - 1; i >= 0; i--){
				var cur = $(connectWith[i]);
				for (var j = cur.length - 1; j >= 0; j--){
					var inst = $.data(cur[j], this.widgetName);
					if(inst && inst != this && !inst.options.disabled) {
						queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper").not('.ui-sortable-placeholder'), inst]);
					}
				};
			};
		}

		queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper").not('.ui-sortable-placeholder'), this]);

		for (var i = queries.length - 1; i >= 0; i--){
			queries[i][0].each(function() {
				items.push(this);
			});
		};

		return $(items);

	},

	_removeCurrentsFromItems: function() {

		var list = this.currentItem.find(":data(" + this.widgetName + "-item)");

		for (var i=0; i < this.items.length; i++) {

			for (var j=0; j < list.length; j++) {
				if(list[j] == this.items[i].item[0])
					this.items.splice(i,1);
			};

		};

	},

	_refreshItems: function(event) {

		this.items = [];
		this.containers = [this];
		var items = this.items;
		var self = this;
		var queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]];
		var connectWith = this._connectWith();

		if(connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down
			for (var i = connectWith.length - 1; i >= 0; i--){
				var cur = $(connectWith[i]);
				for (var j = cur.length - 1; j >= 0; j--){
					var inst = $.data(cur[j], this.widgetName);
					if(inst && inst != this && !inst.options.disabled) {
						queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]);
						this.containers.push(inst);
					}
				};
			};
		}

		for (var i = queries.length - 1; i >= 0; i--) {
			var targetData = queries[i][1];
			var _queries = queries[i][0];

			for (var j=0, queriesLength = _queries.length; j < queriesLength; j++) {
				var item = $(_queries[j]);

				item.data(this.widgetName + '-item', targetData); // Data for target checking (mouse manager)

				items.push({
					item: item,
					instance: targetData,
					width: 0, height: 0,
					left: 0, top: 0
				});
			};
		};

	},

	refreshPositions: function(fast) {

		//This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change
		if(this.offsetParent && this.helper) {
			this.offset.parent = this._getParentOffset();
		}

		for (var i = this.items.length - 1; i >= 0; i--){
			var item = this.items[i];

			//We ignore calculating positions of all connected containers when we're not over them
			if(item.instance != this.currentContainer && this.currentContainer && item.item[0] != this.currentItem[0])
				continue;

			var t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item;

			if (!fast) {
				item.width = t.outerWidth();
				item.height = t.outerHeight();
			}

			var p = t.offset();
			item.left = p.left;
			item.top = p.top;
		};

		if(this.options.custom && this.options.custom.refreshContainers) {
			this.options.custom.refreshContainers.call(this);
		} else {
			for (var i = this.containers.length - 1; i >= 0; i--){
				var p = this.containers[i].element.offset();
				this.containers[i].containerCache.left = p.left;
				this.containers[i].containerCache.top = p.top;
				this.containers[i].containerCache.width	= this.containers[i].element.outerWidth();
				this.containers[i].containerCache.height = this.containers[i].element.outerHeight();
			};
		}

		return this;
	},

	_createPlaceholder: function(that) {

		var self = that || this, o = self.options;

		if(!o.placeholder || o.placeholder.constructor == String) {
			var className = o.placeholder;
			o.placeholder = {
				element: function() {

					var el = $(document.createElement(self.currentItem[0].nodeName))
						.addClass(className || self.currentItem[0].className+" ui-sortable-placeholder")
						.removeClass("ui-sortable-helper")[0];

					if(!className)
						el.style.visibility = "hidden";

					return el;
				},
				update: function(container, p) {

					// 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that
					// 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified
					if(className && !o.forcePlaceholderSize) return;

					//If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item
					if(!p.height()) { p.height(self.currentItem.innerHeight() - parseInt(self.currentItem.css('paddingTop')||0, 10) - parseInt(self.currentItem.css('paddingBottom')||0, 10)); };
					if(!p.width()) { p.width(self.currentItem.innerWidth() - parseInt(self.currentItem.css('paddingLeft')||0, 10) - parseInt(self.currentItem.css('paddingRight')||0, 10)); };
				}
			};
		}

		//Create the placeholder
		self.placeholder = $(o.placeholder.element.call(self.element, self.currentItem));

		//Append it after the actual current item
		self.currentItem.after(self.placeholder);

		//Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)
		o.placeholder.update(self, self.placeholder);

	},

	_contactContainers: function(event) {
		
		// get innermost container that intersects with item 
		var innermostContainer = null, innermostIndex = null;		
		
		
		for (var i = this.containers.length - 1; i >= 0; i--){

			// never consider a container that's located within the item itself 
			if($.ui.contains(this.currentItem[0], this.containers[i].element[0]))
				continue;

			if(this._intersectsWith(this.containers[i].containerCache)) {

				// if we've already found a container and it's more "inner" than this, then continue 
				if(innermostContainer && $.ui.contains(this.containers[i].element[0], innermostContainer.element[0]))
					continue;

				innermostContainer = this.containers[i]; 
				innermostIndex = i;
					
			} else {
				// container doesn't intersect. trigger "out" event if necessary 
				if(this.containers[i].containerCache.over) {
					this.containers[i]._trigger("out", event, this._uiHash(this));
					this.containers[i].containerCache.over = 0;
				}
			}

		}
		
		// if no intersecting containers found, return 
		if(!innermostContainer) return; 

		// move the item into the container if it's not there already
		if(this.containers.length === 1) {
			this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
			this.containers[innermostIndex].containerCache.over = 1;
		} else if(this.currentContainer != this.containers[innermostIndex]) {

			//When entering a new container, we will find the item with the least distance and append our item near it
			var dist = 10000; var itemWithLeastDistance = null; var base = this.positionAbs[this.containers[innermostIndex].floating ? 'left' : 'top'];
			for (var j = this.items.length - 1; j >= 0; j--) {
				if(!$.ui.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) continue;
				var cur = this.containers[innermostIndex].floating ? this.items[j].item.offset().left : this.items[j].item.offset().top;
				if(Math.abs(cur - base) < dist) {
					dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j];
					this.direction = (cur - base > 0) ? 'down' : 'up';
				}
			}

			if(!itemWithLeastDistance && !this.options.dropOnEmpty) //Check if dropOnEmpty is enabled
				return;

			this.currentContainer = this.containers[innermostIndex];
			itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true);
			this._trigger("change", event, this._uiHash());
			this.containers[innermostIndex]._trigger("change", event, this._uiHash(this));

			//Update the placeholder
			this.options.placeholder.update(this.currentContainer, this.placeholder);

			this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
			this.containers[innermostIndex].containerCache.over = 1;
		} 
	
		
	},

	_createHelper: function(event) {

		var o = this.options;
		var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper == 'clone' ? this.currentItem.clone() : this.currentItem);

		if(!helper.parents('body').length) //Add the helper to the DOM if that didn't happen already
			$(o.appendTo != 'parent' ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]);

		if(helper[0] == this.currentItem[0])
			this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") };

		if(helper[0].style.width == '' || o.forceHelperSize) helper.width(this.currentItem.width());
		if(helper[0].style.height == '' || o.forceHelperSize) helper.height(this.currentItem.height());

		return helper;

	},

	_adjustOffsetFromHelper: function(obj) {
		if (typeof obj == 'string') {
			obj = obj.split(' ');
		}
		if ($.isArray(obj)) {
			obj = {left: +obj[0], top: +obj[1] || 0};
		}
		if ('left' in obj) {
			this.offset.click.left = obj.left + this.margins.left;
		}
		if ('right' in obj) {
			this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
		}
		if ('top' in obj) {
			this.offset.click.top = obj.top + this.margins.top;
		}
		if ('bottom' in obj) {
			this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
		}
	},

	_getParentOffset: function() {


		//Get the offsetParent and cache its position
		this.offsetParent = this.helper.offsetParent();
		var po = this.offsetParent.offset();

		// This is a special case where we need to modify a offset calculated on start, since the following happened:
		// 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
		// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
		//    the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
		if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) {
			po.left += this.scrollParent.scrollLeft();
			po.top += this.scrollParent.scrollTop();
		}

		if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information
		|| (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.browser.msie)) //Ugly IE fix
			po = { top: 0, left: 0 };

		return {
			top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
			left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
		};

	},

	_getRelativeOffset: function() {

		if(this.cssPosition == "relative") {
			var p = this.currentItem.position();
			return {
				top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
				left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
			};
		} else {
			return { top: 0, left: 0 };
		}

	},

	_cacheMargins: function() {
		this.margins = {
			left: (parseInt(this.currentItem.css("marginLeft"),10) || 0),
			top: (parseInt(this.currentItem.css("marginTop"),10) || 0)
		};
	},

	_cacheHelperProportions: function() {
		this.helperProportions = {
			width: this.helper.outerWidth(),
			height: this.helper.outerHeight()
		};
	},

	_setContainment: function() {

		var o = this.options;
		if(o.containment == 'parent') o.containment = this.helper[0].parentNode;
		if(o.containment == 'document' || o.containment == 'window') this.containment = [
			0 - this.offset.relative.left - this.offset.parent.left,
			0 - this.offset.relative.top - this.offset.parent.top,
			$(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left,
			($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
		];

		if(!(/^(document|window|parent)$/).test(o.containment)) {
			var ce = $(o.containment)[0];
			var co = $(o.containment).offset();
			var over = ($(ce).css("overflow") != 'hidden');

			this.containment = [
				co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left,
				co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top,
				co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left,
				co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top
			];
		}

	},

	_convertPositionTo: function(d, pos) {

		if(!pos) pos = this.position;
		var mod = d == "absolute" ? 1 : -1;
		var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);

		return {
			top: (
				pos.top																	// The absolute mouse position
				+ this.offset.relative.top * mod										// Only for relative positioned nodes: Relative offset from element to offset parent
				+ this.offset.parent.top * mod											// The offsetParent's offset without borders (offset + border)
				- ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
			),
			left: (
				pos.left																// The absolute mouse position
				+ this.offset.relative.left * mod										// Only for relative positioned nodes: Relative offset from element to offset parent
				+ this.offset.parent.left * mod											// The offsetParent's offset without borders (offset + border)
				- ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
			)
		};

	},

	_generatePosition: function(event) {

		var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);

		// This is another very weird special case that only happens for relative elements:
		// 1. If the css position is relative
		// 2. and the scroll parent is the document or similar to the offset parent
		// we have to refresh the relative offset during the scroll so there are no jumps
		if(this.cssPosition == 'relative' && !(this.scrollParent[0] != document && this.scrollParent[0] != this.offsetParent[0])) {
			this.offset.relative = this._getRelativeOffset();
		}

		var pageX = event.pageX;
		var pageY = event.pageY;

		/*
		 * - Position constraining -
		 * Constrain the position to a mix of grid, containment.
		 */

		if(this.originalPosition) { //If we are not dragging yet, we won't check for options

			if(this.containment) {
				if(event.pageX - this.offset.click.left < this.containment[0]) pageX = this.containment[0] + this.offset.click.left;
				if(event.pageY - this.offset.click.top < this.containment[1]) pageY = this.containment[1] + this.offset.click.top;
				if(event.pageX - this.offset.click.left > this.containment[2]) pageX = this.containment[2] + this.offset.click.left;
				if(event.pageY - this.offset.click.top > this.containment[3]) pageY = this.containment[3] + this.offset.click.top;
			}

			if(o.grid) {
				var top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1];
				pageY = this.containment ? (!(top - this.offset.click.top < this.containment[1] || top - this.offset.click.top > this.containment[3]) ? top : (!(top - this.offset.click.top < this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;

				var left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0];
				pageX = this.containment ? (!(left - this.offset.click.left < this.containment[0] || left - this.offset.click.left > this.containment[2]) ? left : (!(left - this.offset.click.left < this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
			}

		}

		return {
			top: (
				pageY																// The absolute mouse position
				- this.offset.click.top													// Click offset (relative to the element)
				- this.offset.relative.top												// Only for relative positioned nodes: Relative offset from element to offset parent
				- this.offset.parent.top												// The offsetParent's offset without borders (offset + border)
				+ ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
			),
			left: (
				pageX																// The absolute mouse position
				- this.offset.click.left												// Click offset (relative to the element)
				- this.offset.relative.left												// Only for relative positioned nodes: Relative offset from element to offset parent
				- this.offset.parent.left												// The offsetParent's offset without borders (offset + border)
				+ ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
			)
		};

	},

	_rearrange: function(event, i, a, hardRefresh) {

		a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction == 'down' ? i.item[0] : i.item[0].nextSibling));

		//Various things done here to improve the performance:
		// 1. we create a setTimeout, that calls refreshPositions
		// 2. on the instance, we have a counter variable, that get's higher after every append
		// 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same
		// 4. this lets only the last addition to the timeout stack through
		this.counter = this.counter ? ++this.counter : 1;
		var self = this, counter = this.counter;

		window.setTimeout(function() {
			if(counter == self.counter) self.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
		},0);

	},

	_clear: function(event, noPropagation) {

		this.reverting = false;
		// We delay all events that have to be triggered to after the point where the placeholder has been removed and
		// everything else normalized again
		var delayedTriggers = [], self = this;

		// We first have to update the dom position of the actual currentItem
		// Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088)
		if(!this._noFinalSort && this.currentItem.parent().length) this.placeholder.before(this.currentItem);
		this._noFinalSort = null;

		if(this.helper[0] == this.currentItem[0]) {
			for(var i in this._storedCSS) {
				if(this._storedCSS[i] == 'auto' || this._storedCSS[i] == 'static') this._storedCSS[i] = '';
			}
			this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
		} else {
			this.currentItem.show();
		}

		if(this.fromOutside && !noPropagation) delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); });
		if((this.fromOutside || this.domPosition.prev != this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent != this.currentItem.parent()[0]) && !noPropagation) delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed
		if(!$.ui.contains(this.element[0], this.currentItem[0])) { //Node was moved out of the current element
			if(!noPropagation) delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); });
			for (var i = this.containers.length - 1; i >= 0; i--){
				if($.ui.contains(this.containers[i].element[0], this.currentItem[0]) && !noPropagation) {
					delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); };  }).call(this, this.containers[i]));
					delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this));  }; }).call(this, this.containers[i]));
				}
			};
		};

		//Post events to containers
		for (var i = this.containers.length - 1; i >= 0; i--){
			if(!noPropagation) delayedTriggers.push((function(c) { return function(event) { c._trigger("deactivate", event, this._uiHash(this)); };  }).call(this, this.containers[i]));
			if(this.containers[i].containerCache.over) {
				delayedTriggers.push((function(c) { return function(event) { c._trigger("out", event, this._uiHash(this)); };  }).call(this, this.containers[i]));
				this.containers[i].containerCache.over = 0;
			}
		}

		//Do what was originally in plugins
		if(this._storedCursor) $('body').css("cursor", this._storedCursor); //Reset cursor
		if(this._storedOpacity) this.helper.css("opacity", this._storedOpacity); //Reset opacity
		if(this._storedZIndex) this.helper.css("zIndex", this._storedZIndex == 'auto' ? '' : this._storedZIndex); //Reset z-index

		this.dragging = false;
		if(this.cancelHelperRemoval) {
			if(!noPropagation) {
				this._trigger("beforeStop", event, this._uiHash());
				for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
				this._trigger("stop", event, this._uiHash());
			}

			this.fromOutside = false;
			return false;
		}

		if(!noPropagation) this._trigger("beforeStop", event, this._uiHash());

		//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
		this.placeholder[0].parentNode.removeChild(this.placeholder[0]);

		if(this.helper[0] != this.currentItem[0]) this.helper.remove(); this.helper = null;

		if(!noPropagation) {
			for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
			this._trigger("stop", event, this._uiHash());
		}

		this.fromOutside = false;
		return true;

	},

	_trigger: function() {
		if ($.Widget.prototype._trigger.apply(this, arguments) === false) {
			this.cancel();
		}
	},

	_uiHash: function(inst) {
		var self = inst || this;
		return {
			helper: self.helper,
			placeholder: self.placeholder || $([]),
			position: self.position,
			originalPosition: self.originalPosition,
			offset: self.positionAbs,
			item: self.currentItem,
			sender: inst ? inst.element : null
		};
	}

});

$.extend($.ui.sortable, {
	version: "1.8.22"
});

})(jQuery);

;jQuery.effects || (function($, undefined) {

$.effects = {};



/******************************************************************************/
/****************************** COLOR ANIMATIONS ******************************/
/******************************************************************************/

// override the animation for color styles
$.each(['backgroundColor', 'borderBottomColor', 'borderLeftColor',
	'borderRightColor', 'borderTopColor', 'borderColor', 'color', 'outlineColor'],
function(i, attr) {
	$.fx.step[attr] = function(fx) {
		if (!fx.colorInit) {
			fx.start = getColor(fx.elem, attr);
			fx.end = getRGB(fx.end);
			fx.colorInit = true;
		}

		fx.elem.style[attr] = 'rgb(' +
			Math.max(Math.min(parseInt((fx.pos * (fx.end[0] - fx.start[0])) + fx.start[0], 10), 255), 0) + ',' +
			Math.max(Math.min(parseInt((fx.pos * (fx.end[1] - fx.start[1])) + fx.start[1], 10), 255), 0) + ',' +
			Math.max(Math.min(parseInt((fx.pos * (fx.end[2] - fx.start[2])) + fx.start[2], 10), 255), 0) + ')';
	};
});

// Color Conversion functions from highlightFade
// By Blair Mitchelmore
// http://jquery.offput.ca/highlightFade/

// Parse strings looking for color tuples [255,255,255]
function getRGB(color) {
		var result;

		// Check if we're already dealing with an array of colors
		if ( color && color.constructor == Array && color.length == 3 )
				return color;

		// Look for rgb(num,num,num)
		if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color))
				return [parseInt(result[1],10), parseInt(result[2],10), parseInt(result[3],10)];

		// Look for rgb(num%,num%,num%)
		if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color))
				return [parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55];

		// Look for #a0b1c2
		if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color))
				return [parseInt(result[1],16), parseInt(result[2],16), parseInt(result[3],16)];

		// Look for #fff
		if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color))
				return [parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16)];

		// Look for rgba(0, 0, 0, 0) == transparent in Safari 3
		if (result = /rgba\(0, 0, 0, 0\)/.exec(color))
				return colors['transparent'];

		// Otherwise, we're most likely dealing with a named color
		return colors[$.trim(color).toLowerCase()];
}

function getColor(elem, attr) {
		var color;

		do {
				// jQuery <1.4.3 uses curCSS, in 1.4.3 - 1.7.2 curCSS = css, 1.8+ only has css
				color = ($.curCSS || $.css)(elem, attr);

				// Keep going until we find an element that has color, or we hit the body
				if ( color != '' && color != 'transparent' || $.nodeName(elem, "body") )
						break;

				attr = "backgroundColor";
		} while ( elem = elem.parentNode );

		return getRGB(color);
};

// Some named colors to work with
// From Interface by Stefan Petre
// http://interface.eyecon.ro/

var colors = {
	aqua:[0,255,255],
	azure:[240,255,255],
	beige:[245,245,220],
	black:[0,0,0],
	blue:[0,0,255],
	brown:[165,42,42],
	cyan:[0,255,255],
	darkblue:[0,0,139],
	darkcyan:[0,139,139],
	darkgrey:[169,169,169],
	darkgreen:[0,100,0],
	darkkhaki:[189,183,107],
	darkmagenta:[139,0,139],
	darkolivegreen:[85,107,47],
	darkorange:[255,140,0],
	darkorchid:[153,50,204],
	darkred:[139,0,0],
	darksalmon:[233,150,122],
	darkviolet:[148,0,211],
	fuchsia:[255,0,255],
	gold:[255,215,0],
	green:[0,128,0],
	indigo:[75,0,130],
	khaki:[240,230,140],
	lightblue:[173,216,230],
	lightcyan:[224,255,255],
	lightgreen:[144,238,144],
	lightgrey:[211,211,211],
	lightpink:[255,182,193],
	lightyellow:[255,255,224],
	lime:[0,255,0],
	magenta:[255,0,255],
	maroon:[128,0,0],
	navy:[0,0,128],
	olive:[128,128,0],
	orange:[255,165,0],
	pink:[255,192,203],
	purple:[128,0,128],
	violet:[128,0,128],
	red:[255,0,0],
	silver:[192,192,192],
	white:[255,255,255],
	yellow:[255,255,0],
	transparent: [255,255,255]
};



/******************************************************************************/
/****************************** CLASS ANIMATIONS ******************************/
/******************************************************************************/

var classAnimationActions = ['add', 'remove', 'toggle'],
	shorthandStyles = {
		border: 1,
		borderBottom: 1,
		borderColor: 1,
		borderLeft: 1,
		borderRight: 1,
		borderTop: 1,
		borderWidth: 1,
		margin: 1,
		padding: 1
	};

function getElementStyles() {
	var style = document.defaultView
			? document.defaultView.getComputedStyle(this, null)
			: this.currentStyle,
		newStyle = {},
		key,
		camelCase;

	// webkit enumerates style porperties
	if (style && style.length && style[0] && style[style[0]]) {
		var len = style.length;
		while (len--) {
			key = style[len];
			if (typeof style[key] == 'string') {
				camelCase = key.replace(/\-(\w)/g, function(all, letter){
					return letter.toUpperCase();
				});
				newStyle[camelCase] = style[key];
			}
		}
	} else {
		for (key in style) {
			if (typeof style[key] === 'string') {
				newStyle[key] = style[key];
			}
		}
	}
	
	return newStyle;
}

function filterStyles(styles) {
	var name, value;
	for (name in styles) {
		value = styles[name];
		if (
			// ignore null and undefined values
			value == null ||
			// ignore functions (when does this occur?)
			$.isFunction(value) ||
			// shorthand styles that need to be expanded
			name in shorthandStyles ||
			// ignore scrollbars (break in IE)
			(/scrollbar/).test(name) ||

			// only colors or values that can be converted to numbers
			(!(/color/i).test(name) && isNaN(parseFloat(value)))
		) {
			delete styles[name];
		}
	}
	
	return styles;
}

function styleDifference(oldStyle, newStyle) {
	var diff = { _: 0 }, // http://dev.jquery.com/ticket/5459
		name;

	for (name in newStyle) {
		if (oldStyle[name] != newStyle[name]) {
			diff[name] = newStyle[name];
		}
	}

	return diff;
}

$.effects.animateClass = function(value, duration, easing, callback) {
	if ($.isFunction(easing)) {
		callback = easing;
		easing = null;
	}

	return this.queue(function() {
		var that = $(this),
			originalStyleAttr = that.attr('style') || ' ',
			originalStyle = filterStyles(getElementStyles.call(this)),
			newStyle,
			className = that.attr('class') || "";

		$.each(classAnimationActions, function(i, action) {
			if (value[action]) {
				that[action + 'Class'](value[action]);
			}
		});
		newStyle = filterStyles(getElementStyles.call(this));
		that.attr('class', className);

		that.animate(styleDifference(originalStyle, newStyle), {
			queue: false,
			duration: duration,
			easing: easing,
			complete: function() {
				$.each(classAnimationActions, function(i, action) {
					if (value[action]) { that[action + 'Class'](value[action]); }
				});
				// work around bug in IE by clearing the cssText before setting it
				if (typeof that.attr('style') == 'object') {
					that.attr('style').cssText = '';
					that.attr('style').cssText = originalStyleAttr;
				} else {
					that.attr('style', originalStyleAttr);
				}
				if (callback) { callback.apply(this, arguments); }
				$.dequeue( this );
			}
		});
	});
};

$.fn.extend({
	_addClass: $.fn.addClass,
	addClass: function(classNames, speed, easing, callback) {
		return speed ? $.effects.animateClass.apply(this, [{ add: classNames },speed,easing,callback]) : this._addClass(classNames);
	},

	_removeClass: $.fn.removeClass,
	removeClass: function(classNames,speed,easing,callback) {
		return speed ? $.effects.animateClass.apply(this, [{ remove: classNames },speed,easing,callback]) : this._removeClass(classNames);
	},

	_toggleClass: $.fn.toggleClass,
	toggleClass: function(classNames, force, speed, easing, callback) {
		if ( typeof force == "boolean" || force === undefined ) {
			if ( !speed ) {
				// without speed parameter;
				return this._toggleClass(classNames, force);
			} else {
				return $.effects.animateClass.apply(this, [(force?{add:classNames}:{remove:classNames}),speed,easing,callback]);
			}
		} else {
			// without switch parameter;
			return $.effects.animateClass.apply(this, [{ toggle: classNames },force,speed,easing]);
		}
	},

	switchClass: function(remove,add,speed,easing,callback) {
		return $.effects.animateClass.apply(this, [{ add: add, remove: remove },speed,easing,callback]);
	}
});



/******************************************************************************/
/*********************************** EFFECTS **********************************/
/******************************************************************************/

$.extend($.effects, {
	version: "1.8.22",

	// Saves a set of properties in a data storage
	save: function(element, set) {
		for(var i=0; i < set.length; i++) {
			if(set[i] !== null) element.data("ec.storage."+set[i], element[0].style[set[i]]);
		}
	},

	// Restores a set of previously saved properties from a data storage
	restore: function(element, set) {
		for(var i=0; i < set.length; i++) {
			if(set[i] !== null) element.css(set[i], element.data("ec.storage."+set[i]));
		}
	},

	setMode: function(el, mode) {
		if (mode == 'toggle') mode = el.is(':hidden') ? 'show' : 'hide'; // Set for toggle
		return mode;
	},

	getBaseline: function(origin, original) { // Translates a [top,left] array into a baseline value
		// this should be a little more flexible in the future to handle a string & hash
		var y, x;
		switch (origin[0]) {
			case 'top': y = 0; break;
			case 'middle': y = 0.5; break;
			case 'bottom': y = 1; break;
			default: y = origin[0] / original.height;
		};
		switch (origin[1]) {
			case 'left': x = 0; break;
			case 'center': x = 0.5; break;
			case 'right': x = 1; break;
			default: x = origin[1] / original.width;
		};
		return {x: x, y: y};
	},

	// Wraps the element around a wrapper that copies position properties
	createWrapper: function(element) {

		// if the element is already wrapped, return it
		if (element.parent().is('.ui-effects-wrapper')) {
			return element.parent();
		}

		// wrap the element
		var props = {
				width: element.outerWidth(true),
				height: element.outerHeight(true),
				'float': element.css('float')
			},
			wrapper = $('<div></div>')
				.addClass('ui-effects-wrapper')
				.css({
					fontSize: '100%',
					background: 'transparent',
					border: 'none',
					margin: 0,
					padding: 0
				}),
			active = document.activeElement;

		// support: Firefox
		// Firefox incorrectly exposes anonymous content
		// https://bugzilla.mozilla.org/show_bug.cgi?id=561664
		try {
			active.id;
		} catch( e ) {
			active = document.body;
		}

		element.wrap( wrapper );

		// Fixes #7595 - Elements lose focus when wrapped.
		if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
			$( active ).focus();
		}
		
		wrapper = element.parent(); //Hotfix for jQuery 1.4 since some change in wrap() seems to actually loose the reference to the wrapped element

		// transfer positioning properties to the wrapper
		if (element.css('position') == 'static') {
			wrapper.css({ position: 'relative' });
			element.css({ position: 'relative' });
		} else {
			$.extend(props, {
				position: element.css('position'),
				zIndex: element.css('z-index')
			});
			$.each(['top', 'left', 'bottom', 'right'], function(i, pos) {
				props[pos] = element.css(pos);
				if (isNaN(parseInt(props[pos], 10))) {
					props[pos] = 'auto';
				}
			});
			element.css({position: 'relative', top: 0, left: 0, right: 'auto', bottom: 'auto' });
		}

		return wrapper.css(props).show();
	},

	removeWrapper: function(element) {
		var parent,
			active = document.activeElement;
		
		if (element.parent().is('.ui-effects-wrapper')) {
			parent = element.parent().replaceWith(element);
			// Fixes #7595 - Elements lose focus when wrapped.
			if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
				$( active ).focus();
			}
			return parent;
		}
			
		return element;
	},

	setTransition: function(element, list, factor, value) {
		value = value || {};
		$.each(list, function(i, x){
			var unit = element.cssUnit(x);
			if (unit[0] > 0) value[x] = unit[0] * factor + unit[1];
		});
		return value;
	}
});


function _normalizeArguments(effect, options, speed, callback) {
	// shift params for method overloading
	if (typeof effect == 'object') {
		callback = options;
		speed = null;
		options = effect;
		effect = options.effect;
	}
	if ($.isFunction(options)) {
		callback = options;
		speed = null;
		options = {};
	}
        if (typeof options == 'number' || $.fx.speeds[options]) {
		callback = speed;
		speed = options;
		options = {};
	}
	if ($.isFunction(speed)) {
		callback = speed;
		speed = null;
	}

	options = options || {};

	speed = speed || options.duration;
	speed = $.fx.off ? 0 : typeof speed == 'number'
		? speed : speed in $.fx.speeds ? $.fx.speeds[speed] : $.fx.speeds._default;

	callback = callback || options.complete;

	return [effect, options, speed, callback];
}

function standardSpeed( speed ) {
	// valid standard speeds
	if ( !speed || typeof speed === "number" || $.fx.speeds[ speed ] ) {
		return true;
	}
	
	// invalid strings - treat as "normal" speed
	if ( typeof speed === "string" && !$.effects[ speed ] ) {
		return true;
	}
	
	return false;
}

$.fn.extend({
	effect: function(effect, options, speed, callback) {
		var args = _normalizeArguments.apply(this, arguments),
			// TODO: make effects take actual parameters instead of a hash
			args2 = {
				options: args[1],
				duration: args[2],
				callback: args[3]
			},
			mode = args2.options.mode,
			effectMethod = $.effects[effect];
		
		if ( $.fx.off || !effectMethod ) {
			// delegate to the original method (e.g., .show()) if possible
			if ( mode ) {
				return this[ mode ]( args2.duration, args2.callback );
			} else {
				return this.each(function() {
					if ( args2.callback ) {
						args2.callback.call( this );
					}
				});
			}
		}
		
		return effectMethod.call(this, args2);
	},

	_show: $.fn.show,
	show: function(speed) {
		if ( standardSpeed( speed ) ) {
			return this._show.apply(this, arguments);
		} else {
			var args = _normalizeArguments.apply(this, arguments);
			args[1].mode = 'show';
			return this.effect.apply(this, args);
		}
	},

	_hide: $.fn.hide,
	hide: function(speed) {
		if ( standardSpeed( speed ) ) {
			return this._hide.apply(this, arguments);
		} else {
			var args = _normalizeArguments.apply(this, arguments);
			args[1].mode = 'hide';
			return this.effect.apply(this, args);
		}
	},

	// jQuery core overloads toggle and creates _toggle
	__toggle: $.fn.toggle,
	toggle: function(speed) {
		if ( standardSpeed( speed ) || typeof speed === "boolean" || $.isFunction( speed ) ) {
			return this.__toggle.apply(this, arguments);
		} else {
			var args = _normalizeArguments.apply(this, arguments);
			args[1].mode = 'toggle';
			return this.effect.apply(this, args);
		}
	},

	// helper functions
	cssUnit: function(key) {
		var style = this.css(key), val = [];
		$.each( ['em','px','%','pt'], function(i, unit){
			if(style.indexOf(unit) > 0)
				val = [parseFloat(style), unit];
		});
		return val;
	}
});



/******************************************************************************/
/*********************************** EASING ***********************************/
/******************************************************************************/

/*
 * jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/
 *
 * Uses the built in easing capabilities added In jQuery 1.1
 * to offer multiple easing options
 *
 * TERMS OF USE - jQuery Easing
 *
 * Open source under the BSD License.
 *
 * Copyright 2008 George McGinley Smith
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this list of
 * conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, this list
 * of conditions and the following disclaimer in the documentation and/or other materials
 * provided with the distribution.
 *
 * Neither the name of the author nor the names of contributors may be used to endorse
 * or promote products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
*/

// t: current time, b: begInnIng value, c: change In value, d: duration
$.easing.jswing = $.easing.swing;

$.extend($.easing,
{
	def: 'easeOutQuad',
	swing: function (x, t, b, c, d) {
		//alert($.easing.default);
		return $.easing[$.easing.def](x, t, b, c, d);
	},
	easeInQuad: function (x, t, b, c, d) {
		return c*(t/=d)*t + b;
	},
	easeOutQuad: function (x, t, b, c, d) {
		return -c *(t/=d)*(t-2) + b;
	},
	easeInOutQuad: function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return c/2*t*t + b;
		return -c/2 * ((--t)*(t-2) - 1) + b;
	},
	easeInCubic: function (x, t, b, c, d) {
		return c*(t/=d)*t*t + b;
	},
	easeOutCubic: function (x, t, b, c, d) {
		return c*((t=t/d-1)*t*t + 1) + b;
	},
	easeInOutCubic: function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return c/2*t*t*t + b;
		return c/2*((t-=2)*t*t + 2) + b;
	},
	easeInQuart: function (x, t, b, c, d) {
		return c*(t/=d)*t*t*t + b;
	},
	easeOutQuart: function (x, t, b, c, d) {
		return -c * ((t=t/d-1)*t*t*t - 1) + b;
	},
	easeInOutQuart: function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return c/2*t*t*t*t + b;
		return -c/2 * ((t-=2)*t*t*t - 2) + b;
	},
	easeInQuint: function (x, t, b, c, d) {
		return c*(t/=d)*t*t*t*t + b;
	},
	easeOutQuint: function (x, t, b, c, d) {
		return c*((t=t/d-1)*t*t*t*t + 1) + b;
	},
	easeInOutQuint: function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
		return c/2*((t-=2)*t*t*t*t + 2) + b;
	},
	easeInSine: function (x, t, b, c, d) {
		return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
	},
	easeOutSine: function (x, t, b, c, d) {
		return c * Math.sin(t/d * (Math.PI/2)) + b;
	},
	easeInOutSine: function (x, t, b, c, d) {
		return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
	},
	easeInExpo: function (x, t, b, c, d) {
		return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
	},
	easeOutExpo: function (x, t, b, c, d) {
		return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
	},
	easeInOutExpo: function (x, t, b, c, d) {
		if (t==0) return b;
		if (t==d) return b+c;
		if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
		return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
	},
	easeInCirc: function (x, t, b, c, d) {
		return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
	},
	easeOutCirc: function (x, t, b, c, d) {
		return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
	},
	easeInOutCirc: function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
		return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
	},
	easeInElastic: function (x, t, b, c, d) {
		var s=1.70158;var p=0;var a=c;
		if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
		if (a < Math.abs(c)) { a=c; var s=p/4; }
		else var s = p/(2*Math.PI) * Math.asin (c/a);
		return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
	},
	easeOutElastic: function (x, t, b, c, d) {
		var s=1.70158;var p=0;var a=c;
		if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
		if (a < Math.abs(c)) { a=c; var s=p/4; }
		else var s = p/(2*Math.PI) * Math.asin (c/a);
		return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b;
	},
	easeInOutElastic: function (x, t, b, c, d) {
		var s=1.70158;var p=0;var a=c;
		if (t==0) return b;  if ((t/=d/2)==2) return b+c;  if (!p) p=d*(.3*1.5);
		if (a < Math.abs(c)) { a=c; var s=p/4; }
		else var s = p/(2*Math.PI) * Math.asin (c/a);
		if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
		return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b;
	},
	easeInBack: function (x, t, b, c, d, s) {
		if (s == undefined) s = 1.70158;
		return c*(t/=d)*t*((s+1)*t - s) + b;
	},
	easeOutBack: function (x, t, b, c, d, s) {
		if (s == undefined) s = 1.70158;
		return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
	},
	easeInOutBack: function (x, t, b, c, d, s) {
		if (s == undefined) s = 1.70158;
		if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
		return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
	},
	easeInBounce: function (x, t, b, c, d) {
		return c - $.easing.easeOutBounce (x, d-t, 0, c, d) + b;
	},
	easeOutBounce: function (x, t, b, c, d) {
		if ((t/=d) < (1/2.75)) {
			return c*(7.5625*t*t) + b;
		} else if (t < (2/2.75)) {
			return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
		} else if (t < (2.5/2.75)) {
			return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
		} else {
			return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
		}
	},
	easeInOutBounce: function (x, t, b, c, d) {
		if (t < d/2) return $.easing.easeInBounce (x, t*2, 0, c, d) * .5 + b;
		return $.easing.easeOutBounce (x, t*2-d, 0, c, d) * .5 + c*.5 + b;
	}
});

/*
 *
 * TERMS OF USE - EASING EQUATIONS
 *
 * Open source under the BSD License.
 *
 * Copyright 2001 Robert Penner
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this list of
 * conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, this list
 * of conditions and the following disclaimer in the documentation and/or other materials
 * provided with the distribution.
 *
 * Neither the name of the author nor the names of contributors may be used to endorse
 * or promote products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

})(jQuery);

(function( $, undefined ) {

$.effects.blind = function(o) {

	return this.queue(function() {

		// Create element
		var el = $(this), props = ['position','top','bottom','left','right'];

		// Set options
		var mode = $.effects.setMode(el, o.options.mode || 'hide'); // Set Mode
		var direction = o.options.direction || 'vertical'; // Default direction

		// Adjust
		$.effects.save(el, props); el.show(); // Save & Show
		var wrapper = $.effects.createWrapper(el).css({overflow:'hidden'}); // Create Wrapper
		var ref = (direction == 'vertical') ? 'height' : 'width';
		var distance = (direction == 'vertical') ? wrapper.height() : wrapper.width();
		if(mode == 'show') wrapper.css(ref, 0); // Shift

		// Animation
		var animation = {};
		animation[ref] = mode == 'show' ? distance : 0;

		// Animate
		wrapper.animate(animation, o.duration, o.options.easing, function() {
			if(mode == 'hide') el.hide(); // Hide
			$.effects.restore(el, props); $.effects.removeWrapper(el); // Restore
			if(o.callback) o.callback.apply(el[0], arguments); // Callback
			el.dequeue();
		});

	});

};

})(jQuery);

(function( $, undefined ) {

$.effects.bounce = function(o) {

	return this.queue(function() {

		// Create element
		var el = $(this), props = ['position','top','bottom','left','right'];

		// Set options
		var mode = $.effects.setMode(el, o.options.mode || 'effect'); // Set Mode
		var direction = o.options.direction || 'up'; // Default direction
		var distance = o.options.distance || 20; // Default distance
		var times = o.options.times || 5; // Default # of times
		var speed = o.duration || 250; // Default speed per bounce
		if (/show|hide/.test(mode)) props.push('opacity'); // Avoid touching opacity to prevent clearType and PNG issues in IE

		// Adjust
		$.effects.save(el, props); el.show(); // Save & Show
		$.effects.createWrapper(el); // Create Wrapper
		var ref = (direction == 'up' || direction == 'down') ? 'top' : 'left';
		var motion = (direction == 'up' || direction == 'left') ? 'pos' : 'neg';
		var distance = o.options.distance || (ref == 'top' ? el.outerHeight(true) / 3 : el.outerWidth(true) / 3);
		if (mode == 'show') el.css('opacity', 0).css(ref, motion == 'pos' ? -distance : distance); // Shift
		if (mode == 'hide') distance = distance / (times * 2);
		if (mode != 'hide') times--;

		// Animate
		if (mode == 'show') { // Show Bounce
			var animation = {opacity: 1};
			animation[ref] = (motion == 'pos' ? '+=' : '-=') + distance;
			el.animate(animation, speed / 2, o.options.easing);
			distance = distance / 2;
			times--;
		};
		for (var i = 0; i < times; i++) { // Bounces
			var animation1 = {}, animation2 = {};
			animation1[ref] = (motion == 'pos' ? '-=' : '+=') + distance;
			animation2[ref] = (motion == 'pos' ? '+=' : '-=') + distance;
			el.animate(animation1, speed / 2, o.options.easing).animate(animation2, speed / 2, o.options.easing);
			distance = (mode == 'hide') ? distance * 2 : distance / 2;
		};
		if (mode == 'hide') { // Last Bounce
			var animation = {opacity: 0};
			animation[ref] = (motion == 'pos' ? '-=' : '+=')  + distance;
			el.animate(animation, speed / 2, o.options.easing, function(){
				el.hide(); // Hide
				$.effects.restore(el, props); $.effects.removeWrapper(el); // Restore
				if(o.callback) o.callback.apply(this, arguments); // Callback
			});
		} else {
			var animation1 = {}, animation2 = {};
			animation1[ref] = (motion == 'pos' ? '-=' : '+=') + distance;
			animation2[ref] = (motion == 'pos' ? '+=' : '-=') + distance;
			el.animate(animation1, speed / 2, o.options.easing).animate(animation2, speed / 2, o.options.easing, function(){
				$.effects.restore(el, props); $.effects.removeWrapper(el); // Restore
				if(o.callback) o.callback.apply(this, arguments); // Callback
			});
		};
		el.queue('fx', function() { el.dequeue(); });
		el.dequeue();
	});

};

})(jQuery);

(function( $, undefined ) {

$.effects.clip = function(o) {

	return this.queue(function() {

		// Create element
		var el = $(this), props = ['position','top','bottom','left','right','height','width'];

		// Set options
		var mode = $.effects.setMode(el, o.options.mode || 'hide'); // Set Mode
		var direction = o.options.direction || 'vertical'; // Default direction

		// Adjust
		$.effects.save(el, props); el.show(); // Save & Show
		var wrapper = $.effects.createWrapper(el).css({overflow:'hidden'}); // Create Wrapper
		var animate = el[0].tagName == 'IMG' ? wrapper : el;
		var ref = {
			size: (direction == 'vertical') ? 'height' : 'width',
			position: (direction == 'vertical') ? 'top' : 'left'
		};
		var distance = (direction == 'vertical') ? animate.height() : animate.width();
		if(mode == 'show') { animate.css(ref.size, 0); animate.css(ref.position, distance / 2); } // Shift

		// Animation
		var animation = {};
		animation[ref.size] = mode == 'show' ? distance : 0;
		animation[ref.position] = mode == 'show' ? 0 : distance / 2;

		// Animate
		animate.animate(animation, { queue: false, duration: o.duration, easing: o.options.easing, complete: function() {
			if(mode == 'hide') el.hide(); // Hide
			$.effects.restore(el, props); $.effects.removeWrapper(el); // Restore
			if(o.callback) o.callback.apply(el[0], arguments); // Callback
			el.dequeue();
		}});

	});

};

})(jQuery);

(function( $, undefined ) {

$.effects.drop = function(o) {

	return this.queue(function() {

		// Create element
		var el = $(this), props = ['position','top','bottom','left','right','opacity'];

		// Set options
		var mode = $.effects.setMode(el, o.options.mode || 'hide'); // Set Mode
		var direction = o.options.direction || 'left'; // Default Direction

		// Adjust
		$.effects.save(el, props); el.show(); // Save & Show
		$.effects.createWrapper(el); // Create Wrapper
		var ref = (direction == 'up' || direction == 'down') ? 'top' : 'left';
		var motion = (direction == 'up' || direction == 'left') ? 'pos' : 'neg';
		var distance = o.options.distance || (ref == 'top' ? el.outerHeight( true ) / 2 : el.outerWidth( true ) / 2);
		if (mode == 'show') el.css('opacity', 0).css(ref, motion == 'pos' ? -distance : distance); // Shift

		// Animation
		var animation = {opacity: mode == 'show' ? 1 : 0};
		animation[ref] = (mode == 'show' ? (motion == 'pos' ? '+=' : '-=') : (motion == 'pos' ? '-=' : '+=')) + distance;

		// Animate
		el.animate(animation, { queue: false, duration: o.duration, easing: o.options.easing, complete: function() {
			if(mode == 'hide') el.hide(); // Hide
			$.effects.restore(el, props); $.effects.removeWrapper(el); // Restore
			if(o.callback) o.callback.apply(this, arguments); // Callback
			el.dequeue();
		}});

	});

};

})(jQuery);

(function( $, undefined ) {

$.effects.explode = function(o) {

	return this.queue(function() {

	var rows = o.options.pieces ? Math.round(Math.sqrt(o.options.pieces)) : 3;
	var cells = o.options.pieces ? Math.round(Math.sqrt(o.options.pieces)) : 3;

	o.options.mode = o.options.mode == 'toggle' ? ($(this).is(':visible') ? 'hide' : 'show') : o.options.mode;
	var el = $(this).show().css('visibility', 'hidden');
	var offset = el.offset();

	//Substract the margins - not fixing the problem yet.
	offset.top -= parseInt(el.css("marginTop"),10) || 0;
	offset.left -= parseInt(el.css("marginLeft"),10) || 0;

	var width = el.outerWidth(true);
	var height = el.outerHeight(true);

	for(var i=0;i<rows;i++) { // =
		for(var j=0;j<cells;j++) { // ||
			el
				.clone()
				.appendTo('body')
				.wrap('<div></div>')
				.css({
					position: 'absolute',
					visibility: 'visible',
					left: -j*(width/cells),
					top: -i*(height/rows)
				})
				.parent()
				.addClass('ui-effects-explode')
				.css({
					position: 'absolute',
					overflow: 'hidden',
					width: width/cells,
					height: height/rows,
					left: offset.left + j*(width/cells) + (o.options.mode == 'show' ? (j-Math.floor(cells/2))*(width/cells) : 0),
					top: offset.top + i*(height/rows) + (o.options.mode == 'show' ? (i-Math.floor(rows/2))*(height/rows) : 0),
					opacity: o.options.mode == 'show' ? 0 : 1
				}).animate({
					left: offset.left + j*(width/cells) + (o.options.mode == 'show' ? 0 : (j-Math.floor(cells/2))*(width/cells)),
					top: offset.top + i*(height/rows) + (o.options.mode == 'show' ? 0 : (i-Math.floor(rows/2))*(height/rows)),
					opacity: o.options.mode == 'show' ? 1 : 0
				}, o.duration || 500);
		}
	}

	// Set a timeout, to call the callback approx. when the other animations have finished
	setTimeout(function() {

		o.options.mode == 'show' ? el.css({ visibility: 'visible' }) : el.css({ visibility: 'visible' }).hide();
				if(o.callback) o.callback.apply(el[0]); // Callback
				el.dequeue();

				$('div.ui-effects-explode').remove();

	}, o.duration || 500);


	});

};

})(jQuery);

(function( $, undefined ) {

$.effects.fade = function(o) {
	return this.queue(function() {
		var elem = $(this),
			mode = $.effects.setMode(elem, o.options.mode || 'hide');

		elem.animate({ opacity: mode }, {
			queue: false,
			duration: o.duration,
			easing: o.options.easing,
			complete: function() {
				(o.callback && o.callback.apply(this, arguments));
				elem.dequeue();
			}
		});
	});
};

})(jQuery);

(function( $, undefined ) {

$.effects.fold = function(o) {

	return this.queue(function() {

		// Create element
		var el = $(this), props = ['position','top','bottom','left','right'];

		// Set options
		var mode = $.effects.setMode(el, o.options.mode || 'hide'); // Set Mode
		var size = o.options.size || 15; // Default fold size
		var horizFirst = !(!o.options.horizFirst); // Ensure a boolean value
		var duration = o.duration ? o.duration / 2 : $.fx.speeds._default / 2;

		// Adjust
		$.effects.save(el, props); el.show(); // Save & Show
		var wrapper = $.effects.createWrapper(el).css({overflow:'hidden'}); // Create Wrapper
		var widthFirst = ((mode == 'show') != horizFirst);
		var ref = widthFirst ? ['width', 'height'] : ['height', 'width'];
		var distance = widthFirst ? [wrapper.width(), wrapper.height()] : [wrapper.height(), wrapper.width()];
		var percent = /([0-9]+)%/.exec(size);
		if(percent) size = parseInt(percent[1],10) / 100 * distance[mode == 'hide' ? 0 : 1];
		if(mode == 'show') wrapper.css(horizFirst ? {height: 0, width: size} : {height: size, width: 0}); // Shift

		// Animation
		var animation1 = {}, animation2 = {};
		animation1[ref[0]] = mode == 'show' ? distance[0] : size;
		animation2[ref[1]] = mode == 'show' ? distance[1] : 0;

		// Animate
		wrapper.animate(animation1, duration, o.options.easing)
		.animate(animation2, duration, o.options.easing, function() {
			if(mode == 'hide') el.hide(); // Hide
			$.effects.restore(el, props); $.effects.removeWrapper(el); // Restore
			if(o.callback) o.callback.apply(el[0], arguments); // Callback
			el.dequeue();
		});

	});

};

})(jQuery);

(function( $, undefined ) {

$.effects.highlight = function(o) {
	return this.queue(function() {
		var elem = $(this),
			props = ['backgroundImage', 'backgroundColor', 'opacity'],
			mode = $.effects.setMode(elem, o.options.mode || 'show'),
			animation = {
				backgroundColor: elem.css('backgroundColor')
			};

		if (mode == 'hide') {
			animation.opacity = 0;
		}

		$.effects.save(elem, props);
		elem
			.show()
			.css({
				backgroundImage: 'none',
				backgroundColor: o.options.color || '#ffff99'
			})
			.animate(animation, {
				queue: false,
				duration: o.duration,
				easing: o.options.easing,
				complete: function() {
					(mode == 'hide' && elem.hide());
					$.effects.restore(elem, props);
					(mode == 'show' && !$.support.opacity && this.style.removeAttribute('filter'));
					(o.callback && o.callback.apply(this, arguments));
					elem.dequeue();
				}
			});
	});
};

})(jQuery);

(function( $, undefined ) {

$.effects.pulsate = function(o) {
	return this.queue(function() {
		var elem = $(this),
			mode = $.effects.setMode(elem, o.options.mode || 'show'),
			times = ((o.options.times || 5) * 2) - 1,
			duration = o.duration ? o.duration / 2 : $.fx.speeds._default / 2,
			isVisible = elem.is(':visible'),
			animateTo = 0;

		if (!isVisible) {
			elem.css('opacity', 0).show();
			animateTo = 1;
		}

		if ((mode == 'hide' && isVisible) || (mode == 'show' && !isVisible)) {
			times--;
		}

		for (var i = 0; i < times; i++) {
			elem.animate({ opacity: animateTo }, duration, o.options.easing);
			animateTo = (animateTo + 1) % 2;
		}

		elem.animate({ opacity: animateTo }, duration, o.options.easing, function() {
			if (animateTo == 0) {
				elem.hide();
			}
			(o.callback && o.callback.apply(this, arguments));
		});

		elem
			.queue('fx', function() { elem.dequeue(); })
			.dequeue();
	});
};

})(jQuery);

(function( $, undefined ) {

$.effects.puff = function(o) {
	return this.queue(function() {
		var elem = $(this),
			mode = $.effects.setMode(elem, o.options.mode || 'hide'),
			percent = parseInt(o.options.percent, 10) || 150,
			factor = percent / 100,
			original = { height: elem.height(), width: elem.width() };

		$.extend(o.options, {
			fade: true,
			mode: mode,
			percent: mode == 'hide' ? percent : 100,
			from: mode == 'hide'
				? original
				: {
					height: original.height * factor,
					width: original.width * factor
				}
		});

		elem.effect('scale', o.options, o.duration, o.callback);
		elem.dequeue();
	});
};

$.effects.scale = function(o) {

	return this.queue(function() {

		// Create element
		var el = $(this);

		// Set options
		var options = $.extend(true, {}, o.options);
		var mode = $.effects.setMode(el, o.options.mode || 'effect'); // Set Mode
		var percent = parseInt(o.options.percent,10) || (parseInt(o.options.percent,10) == 0 ? 0 : (mode == 'hide' ? 0 : 100)); // Set default scaling percent
		var direction = o.options.direction || 'both'; // Set default axis
		var origin = o.options.origin; // The origin of the scaling
		if (mode != 'effect') { // Set default origin and restore for show/hide
			options.origin = origin || ['middle','center'];
			options.restore = true;
		}
		var original = {height: el.height(), width: el.width()}; // Save original
		el.from = o.options.from || (mode == 'show' ? {height: 0, width: 0} : original); // Default from state

		// Adjust
		var factor = { // Set scaling factor
			y: direction != 'horizontal' ? (percent / 100) : 1,
			x: direction != 'vertical' ? (percent / 100) : 1
		};
		el.to = {height: original.height * factor.y, width: original.width * factor.x}; // Set to state

		if (o.options.fade) { // Fade option to support puff
			if (mode == 'show') {el.from.opacity = 0; el.to.opacity = 1;};
			if (mode == 'hide') {el.from.opacity = 1; el.to.opacity = 0;};
		};

		// Animation
		options.from = el.from; options.to = el.to; options.mode = mode;

		// Animate
		el.effect('size', options, o.duration, o.callback);
		el.dequeue();
	});

};

$.effects.size = function(o) {

	return this.queue(function() {

		// Create element
		var el = $(this), props = ['position','top','bottom','left','right','width','height','overflow','opacity'];
		var props1 = ['position','top','bottom','left','right','overflow','opacity']; // Always restore
		var props2 = ['width','height','overflow']; // Copy for children
		var cProps = ['fontSize'];
		var vProps = ['borderTopWidth', 'borderBottomWidth', 'paddingTop', 'paddingBottom'];
		var hProps = ['borderLeftWidth', 'borderRightWidth', 'paddingLeft', 'paddingRight'];

		// Set options
		var mode = $.effects.setMode(el, o.options.mode || 'effect'); // Set Mode
		var restore = o.options.restore || false; // Default restore
		var scale = o.options.scale || 'both'; // Default scale mode
		var origin = o.options.origin; // The origin of the sizing
		var original = {height: el.height(), width: el.width()}; // Save original
		el.from = o.options.from || original; // Default from state
		el.to = o.options.to || original; // Default to state
		// Adjust
		if (origin) { // Calculate baseline shifts
			var baseline = $.effects.getBaseline(origin, original);
			el.from.top = (original.height - el.from.height) * baseline.y;
			el.from.left = (original.width - el.from.width) * baseline.x;
			el.to.top = (original.height - el.to.height) * baseline.y;
			el.to.left = (original.width - el.to.width) * baseline.x;
		};
		var factor = { // Set scaling factor
			from: {y: el.from.height / original.height, x: el.from.width / original.width},
			to: {y: el.to.height / original.height, x: el.to.width / original.width}
		};
		if (scale == 'box' || scale == 'both') { // Scale the css box
			if (factor.from.y != factor.to.y) { // Vertical props scaling
				props = props.concat(vProps);
				el.from = $.effects.setTransition(el, vProps, factor.from.y, el.from);
				el.to = $.effects.setTransition(el, vProps, factor.to.y, el.to);
			};
			if (factor.from.x != factor.to.x) { // Horizontal props scaling
				props = props.concat(hProps);
				el.from = $.effects.setTransition(el, hProps, factor.from.x, el.from);
				el.to = $.effects.setTransition(el, hProps, factor.to.x, el.to);
			};
		};
		if (scale == 'content' || scale == 'both') { // Scale the content
			if (factor.from.y != factor.to.y) { // Vertical props scaling
				props = props.concat(cProps);
				el.from = $.effects.setTransition(el, cProps, factor.from.y, el.from);
				el.to = $.effects.setTransition(el, cProps, factor.to.y, el.to);
			};
		};
		$.effects.save(el, restore ? props : props1); el.show(); // Save & Show
		$.effects.createWrapper(el); // Create Wrapper
		el.css('overflow','hidden').css(el.from); // Shift

		// Animate
		if (scale == 'content' || scale == 'both') { // Scale the children
			vProps = vProps.concat(['marginTop','marginBottom']).concat(cProps); // Add margins/font-size
			hProps = hProps.concat(['marginLeft','marginRight']); // Add margins
			props2 = props.concat(vProps).concat(hProps); // Concat
			el.find("*[width]").each(function(){
				var child = $(this);
				if (restore) $.effects.save(child, props2);
				var c_original = {height: child.height(), width: child.width()}; // Save original
				child.from = {height: c_original.height * factor.from.y, width: c_original.width * factor.from.x};
				child.to = {height: c_original.height * factor.to.y, width: c_original.width * factor.to.x};
				if (factor.from.y != factor.to.y) { // Vertical props scaling
					child.from = $.effects.setTransition(child, vProps, factor.from.y, child.from);
					child.to = $.effects.setTransition(child, vProps, factor.to.y, child.to);
				};
				if (factor.from.x != factor.to.x) { // Horizontal props scaling
					child.from = $.effects.setTransition(child, hProps, factor.from.x, child.from);
					child.to = $.effects.setTransition(child, hProps, factor.to.x, child.to);
				};
				child.css(child.from); // Shift children
				child.animate(child.to, o.duration, o.options.easing, function(){
					if (restore) $.effects.restore(child, props2); // Restore children
				}); // Animate children
			});
		};

		// Animate
		el.animate(el.to, { queue: false, duration: o.duration, easing: o.options.easing, complete: function() {
			if (el.to.opacity === 0) {
				el.css('opacity', el.from.opacity);
			}
			if(mode == 'hide') el.hide(); // Hide
			$.effects.restore(el, restore ? props : props1); $.effects.removeWrapper(el); // Restore
			if(o.callback) o.callback.apply(this, arguments); // Callback
			el.dequeue();
		}});

	});

};

})(jQuery);

(function( $, undefined ) {

$.effects.shake = function(o) {

	return this.queue(function() {

		// Create element
		var el = $(this), props = ['position','top','bottom','left','right'];

		// Set options
		var mode = $.effects.setMode(el, o.options.mode || 'effect'); // Set Mode
		var direction = o.options.direction || 'left'; // Default direction
		var distance = o.options.distance || 20; // Default distance
		var times = o.options.times || 3; // Default # of times
		var speed = o.duration || o.options.duration || 140; // Default speed per shake

		// Adjust
		$.effects.save(el, props); el.show(); // Save & Show
		$.effects.createWrapper(el); // Create Wrapper
		var ref = (direction == 'up' || direction == 'down') ? 'top' : 'left';
		var motion = (direction == 'up' || direction == 'left') ? 'pos' : 'neg';

		// Animation
		var animation = {}, animation1 = {}, animation2 = {};
		animation[ref] = (motion == 'pos' ? '-=' : '+=')  + distance;
		animation1[ref] = (motion == 'pos' ? '+=' : '-=')  + distance * 2;
		animation2[ref] = (motion == 'pos' ? '-=' : '+=')  + distance * 2;

		// Animate
		el.animate(animation, speed, o.options.easing);
		for (var i = 1; i < times; i++) { // Shakes
			el.animate(animation1, speed, o.options.easing).animate(animation2, speed, o.options.easing);
		};
		el.animate(animation1, speed, o.options.easing).
		animate(animation, speed / 2, o.options.easing, function(){ // Last shake
			$.effects.restore(el, props); $.effects.removeWrapper(el); // Restore
			if(o.callback) o.callback.apply(this, arguments); // Callback
		});
		el.queue('fx', function() { el.dequeue(); });
		el.dequeue();
	});

};

})(jQuery);

(function( $, undefined ) {

$.effects.slide = function(o) {

	return this.queue(function() {

		// Create element
		var el = $(this), props = ['position','top','bottom','left','right'];

		// Set options
		var mode = $.effects.setMode(el, o.options.mode || 'show'); // Set Mode
		var direction = o.options.direction || 'left'; // Default Direction

		// Adjust
		$.effects.save(el, props); el.show(); // Save & Show
		$.effects.createWrapper(el).css({overflow:'hidden'}); // Create Wrapper
		var ref = (direction == 'up' || direction == 'down') ? 'top' : 'left';
		var motion = (direction == 'up' || direction == 'left') ? 'pos' : 'neg';
		var distance = o.options.distance || (ref == 'top' ? el.outerHeight( true ) : el.outerWidth( true ));
		if (mode == 'show') el.css(ref, motion == 'pos' ? (isNaN(distance) ? "-" + distance : -distance) : distance); // Shift

		// Animation
		var animation = {};
		animation[ref] = (mode == 'show' ? (motion == 'pos' ? '+=' : '-=') : (motion == 'pos' ? '-=' : '+=')) + distance;

		// Animate
		el.animate(animation, { queue: false, duration: o.duration, easing: o.options.easing, complete: function() {
			if(mode == 'hide') el.hide(); // Hide
			$.effects.restore(el, props); $.effects.removeWrapper(el); // Restore
			if(o.callback) o.callback.apply(this, arguments); // Callback
			el.dequeue();
		}});

	});

};

})(jQuery);

(function( $, undefined ) {

$.effects.transfer = function(o) {
	return this.queue(function() {
		var elem = $(this),
			target = $(o.options.to),
			endPosition = target.offset(),
			animation = {
				top: endPosition.top,
				left: endPosition.left,
				height: target.innerHeight(),
				width: target.innerWidth()
			},
			startPosition = elem.offset(),
			transfer = $('<div class="ui-effects-transfer"></div>')
				.appendTo(document.body)
				.addClass(o.options.className)
				.css({
					top: startPosition.top,
					left: startPosition.left,
					height: elem.innerHeight(),
					width: elem.innerWidth(),
					position: 'absolute'
				})
				.animate(animation, o.duration, o.options.easing, function() {
					transfer.remove();
					(o.callback && o.callback.apply(elem[0], arguments));
					elem.dequeue();
				});
	});
};

})(jQuery);

(function( $, undefined ) {

$.widget( "ui.accordion", {
	options: {
		active: 0,
		animated: "slide",
		autoHeight: true,
		clearStyle: false,
		collapsible: false,
		event: "click",
		fillSpace: false,
		header: "> li > :first-child,> :not(li):even",
		icons: {
			header: "ui-icon-triangle-1-e",
			headerSelected: "ui-icon-triangle-1-s"
		},
		navigation: false,
		navigationFilter: function() {
			return this.href.toLowerCase() === location.href.toLowerCase();
		}
	},

	_create: function() {
		var self = this,
			options = self.options;

		self.running = 0;

		self.element
			.addClass( "ui-accordion ui-widget ui-helper-reset" )
			// in lack of child-selectors in CSS
			// we need to mark top-LIs in a UL-accordion for some IE-fix
			.children( "li" )
				.addClass( "ui-accordion-li-fix" );

		self.headers = self.element.find( options.header )
			.addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" )
			.bind( "mouseenter.accordion", function() {
				if ( options.disabled ) {
					return;
				}
				$( this ).addClass( "ui-state-hover" );
			})
			.bind( "mouseleave.accordion", function() {
				if ( options.disabled ) {
					return;
				}
				$( this ).removeClass( "ui-state-hover" );
			})
			.bind( "focus.accordion", function() {
				if ( options.disabled ) {
					return;
				}
				$( this ).addClass( "ui-state-focus" );
			})
			.bind( "blur.accordion", function() {
				if ( options.disabled ) {
					return;
				}
				$( this ).removeClass( "ui-state-focus" );
			});

		self.headers.next()
			.addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" );

		if ( options.navigation ) {
			var current = self.element.find( "a" ).filter( options.navigationFilter ).eq( 0 );
			if ( current.length ) {
				var header = current.closest( ".ui-accordion-header" );
				if ( header.length ) {
					// anchor within header
					self.active = header;
				} else {
					// anchor within content
					self.active = current.closest( ".ui-accordion-content" ).prev();
				}
			}
		}

		self.active = self._findActive( self.active || options.active )
			.addClass( "ui-state-default ui-state-active" )
			.toggleClass( "ui-corner-all" )
			.toggleClass( "ui-corner-top" );
		self.active.next().addClass( "ui-accordion-content-active" );

		self._createIcons();
		self.resize();
		
		// ARIA
		self.element.attr( "role", "tablist" );

		self.headers
			.attr( "role", "tab" )
			.bind( "keydown.accordion", function( event ) {
				return self._keydown( event );
			})
			.next()
				.attr( "role", "tabpanel" );

		self.headers
			.not( self.active || "" )
			.attr({
				"aria-expanded": "false",
				"aria-selected": "false",
				tabIndex: -1
			})
			.next()
				.hide();

		// make sure at least one header is in the tab order
		if ( !self.active.length ) {
			self.headers.eq( 0 ).attr( "tabIndex", 0 );
		} else {
			self.active
				.attr({
					"aria-expanded": "true",
					"aria-selected": "true",
					tabIndex: 0
				});
		}

		// only need links in tab order for Safari
		if ( !$.browser.safari ) {
			self.headers.find( "a" ).attr( "tabIndex", -1 );
		}

		if ( options.event ) {
			self.headers.bind( options.event.split(" ").join(".accordion ") + ".accordion", function(event) {
				self._clickHandler.call( self, event, this );
				event.preventDefault();
			});
		}
	},

	_createIcons: function() {
		var options = this.options;
		if ( options.icons ) {
			$( "<span></span>" )
				.addClass( "ui-icon " + options.icons.header )
				.prependTo( this.headers );
			this.active.children( ".ui-icon" )
				.toggleClass(options.icons.header)
				.toggleClass(options.icons.headerSelected);
			this.element.addClass( "ui-accordion-icons" );
		}
	},

	_destroyIcons: function() {
		this.headers.children( ".ui-icon" ).remove();
		this.element.removeClass( "ui-accordion-icons" );
	},

	destroy: function() {
		var options = this.options;

		this.element
			.removeClass( "ui-accordion ui-widget ui-helper-reset" )
			.removeAttr( "role" );

		this.headers
			.unbind( ".accordion" )
			.removeClass( "ui-accordion-header ui-accordion-disabled ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top" )
			.removeAttr( "role" )
			.removeAttr( "aria-expanded" )
			.removeAttr( "aria-selected" )
			.removeAttr( "tabIndex" );

		this.headers.find( "a" ).removeAttr( "tabIndex" );
		this._destroyIcons();
		var contents = this.headers.next()
			.css( "display", "" )
			.removeAttr( "role" )
			.removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled" );
		if ( options.autoHeight || options.fillHeight ) {
			contents.css( "height", "" );
		}

		return $.Widget.prototype.destroy.call( this );
	},

	_setOption: function( key, value ) {
		$.Widget.prototype._setOption.apply( this, arguments );
			
		if ( key == "active" ) {
			this.activate( value );
		}
		if ( key == "icons" ) {
			this._destroyIcons();
			if ( value ) {
				this._createIcons();
			}
		}
		// #5332 - opacity doesn't cascade to positioned elements in IE
		// so we need to add the disabled class to the headers and panels
		if ( key == "disabled" ) {
			this.headers.add(this.headers.next())
				[ value ? "addClass" : "removeClass" ](
					"ui-accordion-disabled ui-state-disabled" );
		}
	},

	_keydown: function( event ) {
		if ( this.options.disabled || event.altKey || event.ctrlKey ) {
			return;
		}

		var keyCode = $.ui.keyCode,
			length = this.headers.length,
			currentIndex = this.headers.index( event.target ),
			toFocus = false;

		switch ( event.keyCode ) {
			case keyCode.RIGHT:
			case keyCode.DOWN:
				toFocus = this.headers[ ( currentIndex + 1 ) % length ];
				break;
			case keyCode.LEFT:
			case keyCode.UP:
				toFocus = this.headers[ ( currentIndex - 1 + length ) % length ];
				break;
			case keyCode.SPACE:
			case keyCode.ENTER:
				this._clickHandler( { target: event.target }, event.target );
				event.preventDefault();
		}

		if ( toFocus ) {
			$( event.target ).attr( "tabIndex", -1 );
			$( toFocus ).attr( "tabIndex", 0 );
			toFocus.focus();
			return false;
		}

		return true;
	},

	resize: function() {
		var options = this.options,
			maxHeight;

		if ( options.fillSpace ) {
			if ( $.browser.msie ) {
				var defOverflow = this.element.parent().css( "overflow" );
				this.element.parent().css( "overflow", "hidden");
			}
			maxHeight = this.element.parent().height();
			if ($.browser.msie) {
				this.element.parent().css( "overflow", defOverflow );
			}

			this.headers.each(function() {
				maxHeight -= $( this ).outerHeight( true );
			});

			this.headers.next()
				.each(function() {
					$( this ).height( Math.max( 0, maxHeight -
						$( this ).innerHeight() + $( this ).height() ) );
				})
				.css( "overflow", "auto" );
		} else if ( options.autoHeight ) {
			maxHeight = 0;
			this.headers.next()
				.each(function() {
					maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() );
				})
				.height( maxHeight );
		}

		return this;
	},

	activate: function( index ) {
		// TODO this gets called on init, changing the option without an explicit call for that
		this.options.active = index;
		// call clickHandler with custom event
		var active = this._findActive( index )[ 0 ];
		this._clickHandler( { target: active }, active );

		return this;
	},

	_findActive: function( selector ) {
		return selector
			? typeof selector === "number"
				? this.headers.filter( ":eq(" + selector + ")" )
				: this.headers.not( this.headers.not( selector ) )
			: selector === false
				? $( [] )
				: this.headers.filter( ":eq(0)" );
	},

	// TODO isn't event.target enough? why the separate target argument?
	_clickHandler: function( event, target ) {
		var options = this.options;
		if ( options.disabled ) {
			return;
		}

		// called only when using activate(false) to close all parts programmatically
		if ( !event.target ) {
			if ( !options.collapsible ) {
				return;
			}
			this.active
				.removeClass( "ui-state-active ui-corner-top" )
				.addClass( "ui-state-default ui-corner-all" )
				.children( ".ui-icon" )
					.removeClass( options.icons.headerSelected )
					.addClass( options.icons.header );
			this.active.next().addClass( "ui-accordion-content-active" );
			var toHide = this.active.next(),
				data = {
					options: options,
					newHeader: $( [] ),
					oldHeader: options.active,
					newContent: $( [] ),
					oldContent: toHide
				},
				toShow = ( this.active = $( [] ) );
			this._toggle( toShow, toHide, data );
			return;
		}

		// get the click target
		var clicked = $( event.currentTarget || target ),
			clickedIsActive = clicked[0] === this.active[0];

		// TODO the option is changed, is that correct?
		// TODO if it is correct, shouldn't that happen after determining that the click is valid?
		options.active = options.collapsible && clickedIsActive ?
			false :
			this.headers.index( clicked );

		// if animations are still active, or the active header is the target, ignore click
		if ( this.running || ( !options.collapsible && clickedIsActive ) ) {
			return;
		}

		// find elements to show and hide
		var active = this.active,
			toShow = clicked.next(),
			toHide = this.active.next(),
			data = {
				options: options,
				newHeader: clickedIsActive && options.collapsible ? $([]) : clicked,
				oldHeader: this.active,
				newContent: clickedIsActive && options.collapsible ? $([]) : toShow,
				oldContent: toHide
			},
			down = this.headers.index( this.active[0] ) > this.headers.index( clicked[0] );

		// when the call to ._toggle() comes after the class changes
		// it causes a very odd bug in IE 8 (see #6720)
		this.active = clickedIsActive ? $([]) : clicked;
		this._toggle( toShow, toHide, data, clickedIsActive, down );

		// switch classes
		active
			.removeClass( "ui-state-active ui-corner-top" )
			.addClass( "ui-state-default ui-corner-all" )
			.children( ".ui-icon" )
				.removeClass( options.icons.headerSelected )
				.addClass( options.icons.header );
		if ( !clickedIsActive ) {
			clicked
				.removeClass( "ui-state-default ui-corner-all" )
				.addClass( "ui-state-active ui-corner-top" )
				.children( ".ui-icon" )
					.removeClass( options.icons.header )
					.addClass( options.icons.headerSelected );
			clicked
				.next()
				.addClass( "ui-accordion-content-active" );
		}

		return;
	},

	_toggle: function( toShow, toHide, data, clickedIsActive, down ) {
		var self = this,
			options = self.options;

		self.toShow = toShow;
		self.toHide = toHide;
		self.data = data;

		var complete = function() {
			if ( !self ) {
				return;
			}
			return self._completed.apply( self, arguments );
		};

		// trigger changestart event
		self._trigger( "changestart", null, self.data );

		// count elements to animate
		self.running = toHide.size() === 0 ? toShow.size() : toHide.size();

		if ( options.animated ) {
			var animOptions = {};

			if ( options.collapsible && clickedIsActive ) {
				animOptions = {
					toShow: $( [] ),
					toHide: toHide,
					complete: complete,
					down: down,
					autoHeight: options.autoHeight || options.fillSpace
				};
			} else {
				animOptions = {
					toShow: toShow,
					toHide: toHide,
					complete: complete,
					down: down,
					autoHeight: options.autoHeight || options.fillSpace
				};
			}

			if ( !options.proxied ) {
				options.proxied = options.animated;
			}

			if ( !options.proxiedDuration ) {
				options.proxiedDuration = options.duration;
			}

			options.animated = $.isFunction( options.proxied ) ?
				options.proxied( animOptions ) :
				options.proxied;

			options.duration = $.isFunction( options.proxiedDuration ) ?
				options.proxiedDuration( animOptions ) :
				options.proxiedDuration;

			var animations = $.ui.accordion.animations,
				duration = options.duration,
				easing = options.animated;

			if ( easing && !animations[ easing ] && !$.easing[ easing ] ) {
				easing = "slide";
			}
			if ( !animations[ easing ] ) {
				animations[ easing ] = function( options ) {
					this.slide( options, {
						easing: easing,
						duration: duration || 700
					});
				};
			}

			animations[ easing ]( animOptions );
		} else {
			if ( options.collapsible && clickedIsActive ) {
				toShow.toggle();
			} else {
				toHide.hide();
				toShow.show();
			}

			complete( true );
		}

		// TODO assert that the blur and focus triggers are really necessary, remove otherwise
		toHide.prev()
			.attr({
				"aria-expanded": "false",
				"aria-selected": "false",
				tabIndex: -1
			})
			.blur();
		toShow.prev()
			.attr({
				"aria-expanded": "true",
				"aria-selected": "true",
				tabIndex: 0
			})
			.focus();
	},

	_completed: function( cancel ) {
		this.running = cancel ? 0 : --this.running;
		if ( this.running ) {
			return;
		}

		if ( this.options.clearStyle ) {
			this.toShow.add( this.toHide ).css({
				height: "",
				overflow: ""
			});
		}

		// other classes are removed before the animation; this one needs to stay until completed
		this.toHide.removeClass( "ui-accordion-content-active" );
		// Work around for rendering bug in IE (#5421)
		if ( this.toHide.length ) {
			this.toHide.parent()[0].className = this.toHide.parent()[0].className;
		}

		this._trigger( "change", null, this.data );
	}
});

$.extend( $.ui.accordion, {
	version: "1.8.22",
	animations: {
		slide: function( options, additions ) {
			options = $.extend({
				easing: "swing",
				duration: 300
			}, options, additions );
			if ( !options.toHide.size() ) {
				options.toShow.animate({
					height: "show",
					paddingTop: "show",
					paddingBottom: "show"
				}, options );
				return;
			}
			if ( !options.toShow.size() ) {
				options.toHide.animate({
					height: "hide",
					paddingTop: "hide",
					paddingBottom: "hide"
				}, options );
				return;
			}
			var overflow = options.toShow.css( "overflow" ),
				percentDone = 0,
				showProps = {},
				hideProps = {},
				fxAttrs = [ "height", "paddingTop", "paddingBottom" ],
				originalWidth;
			// fix width before calculating height of hidden element
			var s = options.toShow;
			originalWidth = s[0].style.width;
			s.width( s.parent().width()
				- parseFloat( s.css( "paddingLeft" ) )
				- parseFloat( s.css( "paddingRight" ) )
				- ( parseFloat( s.css( "borderLeftWidth" ) ) || 0 )
				- ( parseFloat( s.css( "borderRightWidth" ) ) || 0 ) );

			$.each( fxAttrs, function( i, prop ) {
				hideProps[ prop ] = "hide";

				var parts = ( "" + $.css( options.toShow[0], prop ) ).match( /^([\d+-.]+)(.*)$/ );
				showProps[ prop ] = {
					value: parts[ 1 ],
					unit: parts[ 2 ] || "px"
				};
			});
			options.toShow.css({ height: 0, overflow: "hidden" }).show();
			options.toHide
				.filter( ":hidden" )
					.each( options.complete )
				.end()
				.filter( ":visible" )
				.animate( hideProps, {
				step: function( now, settings ) {
					// only calculate the percent when animating height
					// IE gets very inconsistent results when animating elements
					// with small values, which is common for padding
					if ( settings.prop == "height" ) {
						percentDone = ( settings.end - settings.start === 0 ) ? 0 :
							( settings.now - settings.start ) / ( settings.end - settings.start );
					}

					options.toShow[ 0 ].style[ settings.prop ] =
						( percentDone * showProps[ settings.prop ].value )
						+ showProps[ settings.prop ].unit;
				},
				duration: options.duration,
				easing: options.easing,
				complete: function() {
					if ( !options.autoHeight ) {
						options.toShow.css( "height", "" );
					}
					options.toShow.css({
						width: originalWidth,
						overflow: overflow
					});
					options.complete();
				}
			});
		},
		bounceslide: function( options ) {
			this.slide( options, {
				easing: options.down ? "easeOutBounce" : "swing",
				duration: options.down ? 1000 : 200
			});
		}
	}
});

})( jQuery );

(function( $, undefined ) {

// used to prevent race conditions with remote data sources
var requestIndex = 0;

$.widget( "ui.autocomplete", {
	options: {
		appendTo: "body",
		autoFocus: false,
		delay: 300,
		minLength: 1,
		position: {
			my: "left top",
			at: "left bottom",
			collision: "none"
		},
		source: null
	},

	pending: 0,

	_create: function() {
		var self = this,
			doc = this.element[ 0 ].ownerDocument,
			suppressKeyPress;
		this.isMultiLine = this.element.is( "textarea" );

		this.element
			.addClass( "ui-autocomplete-input" )
			.attr( "autocomplete", "off" )
			// TODO verify these actually work as intended
			.attr({
				role: "textbox",
				"aria-autocomplete": "list",
				"aria-haspopup": "true"
			})
			.bind( "keydown.autocomplete", function( event ) {
				if ( self.options.disabled || self.element.propAttr( "readOnly" ) ) {
					return;
				}

				suppressKeyPress = false;
				var keyCode = $.ui.keyCode;
				switch( event.keyCode ) {
				case keyCode.PAGE_UP:
					self._move( "previousPage", event );
					break;
				case keyCode.PAGE_DOWN:
					self._move( "nextPage", event );
					break;
				case keyCode.UP:
					self._keyEvent( "previous", event );
					break;
				case keyCode.DOWN:
					self._keyEvent( "next", event );
					break;
				case keyCode.ENTER:
				case keyCode.NUMPAD_ENTER:
					// when menu is open and has focus
					if ( self.menu.active ) {
						// #6055 - Opera still allows the keypress to occur
						// which causes forms to submit
						suppressKeyPress = true;
						event.preventDefault();
					}
					//passthrough - ENTER and TAB both select the current element
				case keyCode.TAB:
					if ( !self.menu.active ) {
						return;
					}
					self.menu.select( event );
					break;
				case keyCode.ESCAPE:
					self.element.val( self.term );
					self.close( event );
					break;
				default:
					// keypress is triggered before the input value is changed
					clearTimeout( self.searching );
					self.searching = setTimeout(function() {
						// only search if the value has changed
						if ( self.term != self.element.val() ) {
							self.selectedItem = null;
							self.search( null, event );
						}
					}, self.options.delay );
					break;
				}
			})
			.bind( "keypress.autocomplete", function( event ) {
				if ( suppressKeyPress ) {
					suppressKeyPress = false;
					event.preventDefault();
				}
			})
			.bind( "focus.autocomplete", function() {
				if ( self.options.disabled ) {
					return;
				}

				self.selectedItem = null;
				self.previous = self.element.val();
			})
			.bind( "blur.autocomplete", function( event ) {
				if ( self.options.disabled ) {
					return;
				}

				clearTimeout( self.searching );
				// clicks on the menu (or a button to trigger a search) will cause a blur event
				self.closing = setTimeout(function() {
					self.close( event );
					self._change( event );
				}, 150 );
			});
		this._initSource();
		this.menu = $( "<ul></ul>" )
			.addClass( "ui-autocomplete" )
			.appendTo( $( this.options.appendTo || "body", doc )[0] )
			// prevent the close-on-blur in case of a "slow" click on the menu (long mousedown)
			.mousedown(function( event ) {
				// clicking on the scrollbar causes focus to shift to the body
				// but we can't detect a mouseup or a click immediately afterward
				// so we have to track the next mousedown and close the menu if
				// the user clicks somewhere outside of the autocomplete
				var menuElement = self.menu.element[ 0 ];
				if ( !$( event.target ).closest( ".ui-menu-item" ).length ) {
					setTimeout(function() {
						$( document ).one( 'mousedown', function( event ) {
							if ( event.target !== self.element[ 0 ] &&
								event.target !== menuElement &&
								!$.ui.contains( menuElement, event.target ) ) {
								self.close();
							}
						});
					}, 1 );
				}

				// use another timeout to make sure the blur-event-handler on the input was already triggered
				setTimeout(function() {
					clearTimeout( self.closing );
				}, 13);
			})
			.menu({
				focus: function( event, ui ) {
					var item = ui.item.data( "item.autocomplete" );
					if ( false !== self._trigger( "focus", event, { item: item } ) ) {
						// use value to match what will end up in the input, if it was a key event
						if ( /^key/.test(event.originalEvent.type) ) {
							self.element.val( item.value );
						}
					}
				},
				selected: function( event, ui ) {
					var item = ui.item.data( "item.autocomplete" ),
						previous = self.previous;

					// only trigger when focus was lost (click on menu)
					if ( self.element[0] !== doc.activeElement ) {
						self.element.focus();
						self.previous = previous;
						// #6109 - IE triggers two focus events and the second
						// is asynchronous, so we need to reset the previous
						// term synchronously and asynchronously :-(
						setTimeout(function() {
							self.previous = previous;
							self.selectedItem = item;
						}, 1);
					}

					if ( false !== self._trigger( "select", event, { item: item } ) ) {
						self.element.val( item.value );
					}
					// reset the term after the select event
					// this allows custom select handling to work properly
					self.term = self.element.val();

					self.close( event );
					self.selectedItem = item;
				},
				blur: function( event, ui ) {
					// don't set the value of the text field if it's already correct
					// this prevents moving the cursor unnecessarily
					if ( self.menu.element.is(":visible") &&
						( self.element.val() !== self.term ) ) {
						self.element.val( self.term );
					}
				}
			})
			.zIndex( this.element.zIndex() + 1 )
			// workaround for jQuery bug #5781 http://dev.jquery.com/ticket/5781
			.css({ top: 0, left: 0 })
			.hide()
			.data( "menu" );
		if ( $.fn.bgiframe ) {
			 this.menu.element.bgiframe();
		}
		// turning off autocomplete prevents the browser from remembering the
		// value when navigating through history, so we re-enable autocomplete
		// if the page is unloaded before the widget is destroyed. #7790
		self.beforeunloadHandler = function() {
			self.element.removeAttr( "autocomplete" );
		};
		$( window ).bind( "beforeunload", self.beforeunloadHandler );
	},

	destroy: function() {
		this.element
			.removeClass( "ui-autocomplete-input" )
			.removeAttr( "autocomplete" )
			.removeAttr( "role" )
			.removeAttr( "aria-autocomplete" )
			.removeAttr( "aria-haspopup" );
		this.menu.element.remove();
		$( window ).unbind( "beforeunload", this.beforeunloadHandler );
		$.Widget.prototype.destroy.call( this );
	},

	_setOption: function( key, value ) {
		$.Widget.prototype._setOption.apply( this, arguments );
		if ( key === "source" ) {
			this._initSource();
		}
		if ( key === "appendTo" ) {
			this.menu.element.appendTo( $( value || "body", this.element[0].ownerDocument )[0] )
		}
		if ( key === "disabled" && value && this.xhr ) {
			this.xhr.abort();
		}
	},

	_initSource: function() {
		var self = this,
			array,
			url;
		if ( $.isArray(this.options.source) ) {
			array = this.options.source;
			this.source = function( request, response ) {
				response( $.ui.autocomplete.filter(array, request.term) );
			};
		} else if ( typeof this.options.source === "string" ) {
			url = this.options.source;
			this.source = function( request, response ) {
				if ( self.xhr ) {
					self.xhr.abort();
				}
				self.xhr = $.ajax({
					url: url,
					data: request,
					dataType: "json",
					success: function( data, status ) {
						response( data );
					},
					error: function() {
						response( [] );
					}
				});
			};
		} else {
			this.source = this.options.source;
		}
	},

	search: function( value, event ) {
		value = value != null ? value : this.element.val();

		// always save the actual value, not the one passed as an argument
		this.term = this.element.val();

		if ( value.length < this.options.minLength ) {
			return this.close( event );
		}

		clearTimeout( this.closing );
		if ( this._trigger( "search", event ) === false ) {
			return;
		}

		return this._search( value );
	},

	_search: function( value ) {
		this.pending++;
		this.element.addClass( "ui-autocomplete-loading" );

		this.source( { term: value }, this._response() );
	},

	_response: function() {
		var that = this,
			index = ++requestIndex;

		return function( content ) {
			if ( index === requestIndex ) {
				that.__response( content );
			}

			that.pending--;
			if ( !that.pending ) {
				that.element.removeClass( "ui-autocomplete-loading" );
			}
		};
	},

	__response: function( content ) {
		if ( !this.options.disabled && content && content.length ) {
			content = this._normalize( content );
			this._suggest( content );
			this._trigger( "open" );
		} else {
			this.close();
		}
	},

	close: function( event ) {
		clearTimeout( this.closing );
		if ( this.menu.element.is(":visible") ) {
			this.menu.element.hide();
			this.menu.deactivate();
			this._trigger( "close", event );
		}
	},
	
	_change: function( event ) {
		if ( this.previous !== this.element.val() ) {
			this._trigger( "change", event, { item: this.selectedItem } );
		}
	},

	_normalize: function( items ) {
		// assume all items have the right format when the first item is complete
		if ( items.length && items[0].label && items[0].value ) {
			return items;
		}
		return $.map( items, function(item) {
			if ( typeof item === "string" ) {
				return {
					label: item,
					value: item
				};
			}
			return $.extend({
				label: item.label || item.value,
				value: item.value || item.label
			}, item );
		});
	},

	_suggest: function( items ) {
		var ul = this.menu.element
			.empty()
			.zIndex( this.element.zIndex() + 1 );
		this._renderMenu( ul, items );
		// TODO refresh should check if the active item is still in the dom, removing the need for a manual deactivate
		this.menu.deactivate();
		this.menu.refresh();

		// size and position menu
		ul.show();
		this._resizeMenu();
		ul.position( $.extend({
			of: this.element
		}, this.options.position ));

		if ( this.options.autoFocus ) {
			this.menu.next( new $.Event("mouseover") );
		}
	},

	_resizeMenu: function() {
		var ul = this.menu.element;
		ul.outerWidth( Math.max(
			// Firefox wraps long text (possibly a rounding bug)
			// so we add 1px to avoid the wrapping (#7513)
			ul.width( "" ).outerWidth() + 1,
			this.element.outerWidth()
		) );
	},

	_renderMenu: function( ul, items ) {
		var self = this;
		$.each( items, function( index, item ) {
			self._renderItem( ul, item );
		});
	},

	_renderItem: function( ul, item) {
		return $( "<li></li>" )
			.data( "item.autocomplete", item )
			.append( $( "<a></a>" ).text( item.label ) )
			.appendTo( ul );
	},

	_move: function( direction, event ) {
		if ( !this.menu.element.is(":visible") ) {
			this.search( null, event );
			return;
		}
		if ( this.menu.first() && /^previous/.test(direction) ||
				this.menu.last() && /^next/.test(direction) ) {
			this.element.val( this.term );
			this.menu.deactivate();
			return;
		}
		this.menu[ direction ]( event );
	},

	widget: function() {
		return this.menu.element;
	},
	_keyEvent: function( keyEvent, event ) {
		if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
			this._move( keyEvent, event );

			// prevents moving cursor to beginning/end of the text field in some browsers
			event.preventDefault();
		}
	}
});

$.extend( $.ui.autocomplete, {
	escapeRegex: function( value ) {
		return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
	},
	filter: function(array, term) {
		var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
		return $.grep( array, function(value) {
			return matcher.test( value.label || value.value || value );
		});
	}
});

}( jQuery ));

/*
 * jQuery UI Menu (not officially released)
 * 
 * This widget isn't yet finished and the API is subject to change. We plan to finish
 * it for the next release. You're welcome to give it a try anyway and give us feedback,
 * as long as you're okay with migrating your code later on. We can help with that, too.
 *
 * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://jquery.org/license
 *
 * http://docs.jquery.com/UI/Menu
 *
 * Depends:
 *	jquery.ui.core.js
 *  jquery.ui.widget.js
 */
(function($) {

$.widget("ui.menu", {
	_create: function() {
		var self = this;
		this.element
			.addClass("ui-menu ui-widget ui-widget-content ui-corner-all")
			.attr({
				role: "listbox",
				"aria-activedescendant": "ui-active-menuitem"
			})
			.click(function( event ) {
				if ( !$( event.target ).closest( ".ui-menu-item a" ).length ) {
					return;
				}
				// temporary
				event.preventDefault();
				self.select( event );
			});
		this.refresh();
	},
	
	refresh: function() {
		var self = this;

		// don't refresh list items that are already adapted
		var items = this.element.children("li:not(.ui-menu-item):has(a)")
			.addClass("ui-menu-item")
			.attr("role", "menuitem");
		
		items.children("a")
			.addClass("ui-corner-all")
			.attr("tabindex", -1)
			// mouseenter doesn't work with event delegation
			.mouseenter(function( event ) {
				self.activate( event, $(this).parent() );
			})
			.mouseleave(function() {
				self.deactivate();
			});
	},

	activate: function( event, item ) {
		this.deactivate();
		if (this.hasScroll()) {
			var offset = item.offset().top - this.element.offset().top,
				scroll = this.element.scrollTop(),
				elementHeight = this.element.height();
			if (offset < 0) {
				this.element.scrollTop( scroll + offset);
			} else if (offset >= elementHeight) {
				this.element.scrollTop( scroll + offset - elementHeight + item.height());
			}
		}
		this.active = item.eq(0)
			.children("a")
				.addClass("ui-state-hover")
				.attr("id", "ui-active-menuitem")
			.end();
		this._trigger("focus", event, { item: item });
	},

	deactivate: function() {
		if (!this.active) { return; }

		this.active.children("a")
			.removeClass("ui-state-hover")
			.removeAttr("id");
		this._trigger("blur");
		this.active = null;
	},

	next: function(event) {
		this.move("next", ".ui-menu-item:first", event);
	},

	previous: function(event) {
		this.move("prev", ".ui-menu-item:last", event);
	},

	first: function() {
		return this.active && !this.active.prevAll(".ui-menu-item").length;
	},

	last: function() {
		return this.active && !this.active.nextAll(".ui-menu-item").length;
	},

	move: function(direction, edge, event) {
		if (!this.active) {
			this.activate(event, this.element.children(edge));
			return;
		}
		var next = this.active[direction + "All"](".ui-menu-item").eq(0);
		if (next.length) {
			this.activate(event, next);
		} else {
			this.activate(event, this.element.children(edge));
		}
	},

	// TODO merge with previousPage
	nextPage: function(event) {
		if (this.hasScroll()) {
			// TODO merge with no-scroll-else
			if (!this.active || this.last()) {
				this.activate(event, this.element.children(".ui-menu-item:first"));
				return;
			}
			var base = this.active.offset().top,
				height = this.element.height(),
				result = this.element.children(".ui-menu-item").filter(function() {
					var close = $(this).offset().top - base - height + $(this).height();
					// TODO improve approximation
					return close < 10 && close > -10;
				});

			// TODO try to catch this earlier when scrollTop indicates the last page anyway
			if (!result.length) {
				result = this.element.children(".ui-menu-item:last");
			}
			this.activate(event, result);
		} else {
			this.activate(event, this.element.children(".ui-menu-item")
				.filter(!this.active || this.last() ? ":first" : ":last"));
		}
	},

	// TODO merge with nextPage
	previousPage: function(event) {
		if (this.hasScroll()) {
			// TODO merge with no-scroll-else
			if (!this.active || this.first()) {
				this.activate(event, this.element.children(".ui-menu-item:last"));
				return;
			}

			var base = this.active.offset().top,
				height = this.element.height(),
				result = this.element.children(".ui-menu-item").filter(function() {
					var close = $(this).offset().top - base + height - $(this).height();
					// TODO improve approximation
					return close < 10 && close > -10;
				});

			// TODO try to catch this earlier when scrollTop indicates the last page anyway
			if (!result.length) {
				result = this.element.children(".ui-menu-item:first");
			}
			this.activate(event, result);
		} else {
			this.activate(event, this.element.children(".ui-menu-item")
				.filter(!this.active || this.first() ? ":last" : ":first"));
		}
	},

	hasScroll: function() {
		return this.element.height() < this.element[ $.fn.prop ? "prop" : "attr" ]("scrollHeight");
	},

	select: function( event ) {
		this._trigger("selected", event, { item: this.active });
	}
});

}(jQuery));

(function( $, undefined ) {

var lastActive, startXPos, startYPos, clickDragged,
	baseClasses = "ui-button ui-widget ui-state-default ui-corner-all",
	stateClasses = "ui-state-hover ui-state-active ",
	typeClasses = "ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only",
	formResetHandler = function() {
		var buttons = $( this ).find( ":ui-button" );
		setTimeout(function() {
			buttons.button( "refresh" );
		}, 1 );
	},
	radioGroup = function( radio ) {
		var name = radio.name,
			form = radio.form,
			radios = $( [] );
		if ( name ) {
			if ( form ) {
				radios = $( form ).find( "[name='" + name + "']" );
			} else {
				radios = $( "[name='" + name + "']", radio.ownerDocument )
					.filter(function() {
						return !this.form;
					});
			}
		}
		return radios;
	};

$.widget( "ui.button", {
	options: {
		disabled: null,
		text: true,
		label: null,
		icons: {
			primary: null,
			secondary: null
		}
	},
	_create: function() {
		this.element.closest( "form" )
			.unbind( "reset.button" )
			.bind( "reset.button", formResetHandler );

		if ( typeof this.options.disabled !== "boolean" ) {
			this.options.disabled = !!this.element.propAttr( "disabled" );
		} else {
			this.element.propAttr( "disabled", this.options.disabled );
		}

		this._determineButtonType();
		this.hasTitle = !!this.buttonElement.attr( "title" );

		var self = this,
			options = this.options,
			toggleButton = this.type === "checkbox" || this.type === "radio",
			hoverClass = "ui-state-hover" + ( !toggleButton ? " ui-state-active" : "" ),
			focusClass = "ui-state-focus";

		if ( options.label === null ) {
			options.label = this.buttonElement.html();
		}

		this.buttonElement
			.addClass( baseClasses )
			.attr( "role", "button" )
			.bind( "mouseenter.button", function() {
				if ( options.disabled ) {
					return;
				}
				$( this ).addClass( "ui-state-hover" );
				if ( this === lastActive ) {
					$( this ).addClass( "ui-state-active" );
				}
			})
			.bind( "mouseleave.button", function() {
				if ( options.disabled ) {
					return;
				}
				$( this ).removeClass( hoverClass );
			})
			.bind( "click.button", function( event ) {
				if ( options.disabled ) {
					event.preventDefault();
					event.stopImmediatePropagation();
				}
			});

		this.element
			.bind( "focus.button", function() {
				// no need to check disabled, focus won't be triggered anyway
				self.buttonElement.addClass( focusClass );
			})
			.bind( "blur.button", function() {
				self.buttonElement.removeClass( focusClass );
			});

		if ( toggleButton ) {
			this.element.bind( "change.button", function() {
				if ( clickDragged ) {
					return;
				}
				self.refresh();
			});
			// if mouse moves between mousedown and mouseup (drag) set clickDragged flag
			// prevents issue where button state changes but checkbox/radio checked state
			// does not in Firefox (see ticket #6970)
			this.buttonElement
				.bind( "mousedown.button", function( event ) {
					if ( options.disabled ) {
						return;
					}
					clickDragged = false;
					startXPos = event.pageX;
					startYPos = event.pageY;
				})
				.bind( "mouseup.button", function( event ) {
					if ( options.disabled ) {
						return;
					}
					if ( startXPos !== event.pageX || startYPos !== event.pageY ) {
						clickDragged = true;
					}
			});
		}

		if ( this.type === "checkbox" ) {
			this.buttonElement.bind( "click.button", function() {
				if ( options.disabled || clickDragged ) {
					return false;
				}
				$( this ).toggleClass( "ui-state-active" );
				self.buttonElement.attr( "aria-pressed", self.element[0].checked );
			});
		} else if ( this.type === "radio" ) {
			this.buttonElement.bind( "click.button", function() {
				if ( options.disabled || clickDragged ) {
					return false;
				}
				$( this ).addClass( "ui-state-active" );
				self.buttonElement.attr( "aria-pressed", "true" );

				var radio = self.element[ 0 ];
				radioGroup( radio )
					.not( radio )
					.map(function() {
						return $( this ).button( "widget" )[ 0 ];
					})
					.removeClass( "ui-state-active" )
					.attr( "aria-pressed", "false" );
			});
		} else {
			this.buttonElement
				.bind( "mousedown.button", function() {
					if ( options.disabled ) {
						return false;
					}
					$( this ).addClass( "ui-state-active" );
					lastActive = this;
					$( document ).one( "mouseup", function() {
						lastActive = null;
					});
				})
				.bind( "mouseup.button", function() {
					if ( options.disabled ) {
						return false;
					}
					$( this ).removeClass( "ui-state-active" );
				})
				.bind( "keydown.button", function(event) {
					if ( options.disabled ) {
						return false;
					}
					if ( event.keyCode == $.ui.keyCode.SPACE || event.keyCode == $.ui.keyCode.ENTER ) {
						$( this ).addClass( "ui-state-active" );
					}
				})
				.bind( "keyup.button", function() {
					$( this ).removeClass( "ui-state-active" );
				});

			if ( this.buttonElement.is("a") ) {
				this.buttonElement.keyup(function(event) {
					if ( event.keyCode === $.ui.keyCode.SPACE ) {
						// TODO pass through original event correctly (just as 2nd argument doesn't work)
						$( this ).click();
					}
				});
			}
		}

		// TODO: pull out $.Widget's handling for the disabled option into
		// $.Widget.prototype._setOptionDisabled so it's easy to proxy and can
		// be overridden by individual plugins
		this._setOption( "disabled", options.disabled );
		this._resetButton();
	},

	_determineButtonType: function() {

		if ( this.element.is(":checkbox") ) {
			this.type = "checkbox";
		} else if ( this.element.is(":radio") ) {
			this.type = "radio";
		} else if ( this.element.is("input") ) {
			this.type = "input";
		} else {
			this.type = "button";
		}

		if ( this.type === "checkbox" || this.type === "radio" ) {
			// we don't search against the document in case the element
			// is disconnected from the DOM
			var ancestor = this.element.parents().filter(":last"),
				labelSelector = "label[for='" + this.element.attr("id") + "']";
			this.buttonElement = ancestor.find( labelSelector );
			if ( !this.buttonElement.length ) {
				ancestor = ancestor.length ? ancestor.siblings() : this.element.siblings();
				this.buttonElement = ancestor.filter( labelSelector );
				if ( !this.buttonElement.length ) {
					this.buttonElement = ancestor.find( labelSelector );
				}
			}
			this.element.addClass( "ui-helper-hidden-accessible" );

			var checked = this.element.is( ":checked" );
			if ( checked ) {
				this.buttonElement.addClass( "ui-state-active" );
			}
			this.buttonElement.attr( "aria-pressed", checked );
		} else {
			this.buttonElement = this.element;
		}
	},

	widget: function() {
		return this.buttonElement;
	},

	destroy: function() {
		this.element
			.removeClass( "ui-helper-hidden-accessible" );
		this.buttonElement
			.removeClass( baseClasses + " " + stateClasses + " " + typeClasses )
			.removeAttr( "role" )
			.removeAttr( "aria-pressed" )
			.html( this.buttonElement.find(".ui-button-text").html() );

		if ( !this.hasTitle ) {
			this.buttonElement.removeAttr( "title" );
		}

		$.Widget.prototype.destroy.call( this );
	},

	_setOption: function( key, value ) {
		$.Widget.prototype._setOption.apply( this, arguments );
		if ( key === "disabled" ) {
			if ( value ) {
				this.element.propAttr( "disabled", true );
			} else {
				this.element.propAttr( "disabled", false );
			}
			return;
		}
		this._resetButton();
	},

	refresh: function() {
		var isDisabled = this.element.is( ":disabled" );
		if ( isDisabled !== this.options.disabled ) {
			this._setOption( "disabled", isDisabled );
		}
		if ( this.type === "radio" ) {
			radioGroup( this.element[0] ).each(function() {
				if ( $( this ).is( ":checked" ) ) {
					$( this ).button( "widget" )
						.addClass( "ui-state-active" )
						.attr( "aria-pressed", "true" );
				} else {
					$( this ).button( "widget" )
						.removeClass( "ui-state-active" )
						.attr( "aria-pressed", "false" );
				}
			});
		} else if ( this.type === "checkbox" ) {
			if ( this.element.is( ":checked" ) ) {
				this.buttonElement
					.addClass( "ui-state-active" )
					.attr( "aria-pressed", "true" );
			} else {
				this.buttonElement
					.removeClass( "ui-state-active" )
					.attr( "aria-pressed", "false" );
			}
		}
	},

	_resetButton: function() {
		if ( this.type === "input" ) {
			if ( this.options.label ) {
				this.element.val( this.options.label );
			}
			return;
		}
		var buttonElement = this.buttonElement.removeClass( typeClasses ),
			buttonText = $( "<span></span>", this.element[0].ownerDocument )
				.addClass( "ui-button-text" )
				.html( this.options.label )
				.appendTo( buttonElement.empty() )
				.text(),
			icons = this.options.icons,
			multipleIcons = icons.primary && icons.secondary,
			buttonClasses = [];  

		if ( icons.primary || icons.secondary ) {
			if ( this.options.text ) {
				buttonClasses.push( "ui-button-text-icon" + ( multipleIcons ? "s" : ( icons.primary ? "-primary" : "-secondary" ) ) );
			}

			if ( icons.primary ) {
				buttonElement.prepend( "<span class='ui-button-icon-primary ui-icon " + icons.primary + "'></span>" );
			}

			if ( icons.secondary ) {
				buttonElement.append( "<span class='ui-button-icon-secondary ui-icon " + icons.secondary + "'></span>" );
			}

			if ( !this.options.text ) {
				buttonClasses.push( multipleIcons ? "ui-button-icons-only" : "ui-button-icon-only" );

				if ( !this.hasTitle ) {
					buttonElement.attr( "title", buttonText );
				}
			}
		} else {
			buttonClasses.push( "ui-button-text-only" );
		}
		buttonElement.addClass( buttonClasses.join( " " ) );
	}
});

$.widget( "ui.buttonset", {
	options: {
		items: ":button, :submit, :reset, :checkbox, :radio, a, :data(button)"
	},

	_create: function() {
		this.element.addClass( "ui-buttonset" );
	},
	
	_init: function() {
		this.refresh();
	},

	_setOption: function( key, value ) {
		if ( key === "disabled" ) {
			this.buttons.button( "option", key, value );
		}

		$.Widget.prototype._setOption.apply( this, arguments );
	},
	
	refresh: function() {
		var rtl = this.element.css( "direction" ) === "rtl";
		
		this.buttons = this.element.find( this.options.items )
			.filter( ":ui-button" )
				.button( "refresh" )
			.end()
			.not( ":ui-button" )
				.button()
			.end()
			.map(function() {
				return $( this ).button( "widget" )[ 0 ];
			})
				.removeClass( "ui-corner-all ui-corner-left ui-corner-right" )
				.filter( ":first" )
					.addClass( rtl ? "ui-corner-right" : "ui-corner-left" )
				.end()
				.filter( ":last" )
					.addClass( rtl ? "ui-corner-left" : "ui-corner-right" )
				.end()
			.end();
	},

	destroy: function() {
		this.element.removeClass( "ui-buttonset" );
		this.buttons
			.map(function() {
				return $( this ).button( "widget" )[ 0 ];
			})
				.removeClass( "ui-corner-left ui-corner-right" )
			.end()
			.button( "destroy" );

		$.Widget.prototype.destroy.call( this );
	}
});

}( jQuery ) );

(function( $, undefined ) {

$.extend($.ui, { datepicker: { version: "1.8.22" } });

var PROP_NAME = 'datepicker';
var dpuuid = new Date().getTime();
var instActive;

/* Date picker manager.
   Use the singleton instance of this class, $.datepicker, to interact with the date picker.
   Settings for (groups of) date pickers are maintained in an instance object,
   allowing multiple different settings on the same page. */

function Datepicker() {
	this.debug = false; // Change this to true to start debugging
	this._curInst = null; // The current instance in use
	this._keyEvent = false; // If the last event was a key event
	this._disabledInputs = []; // List of date picker inputs that have been disabled
	this._datepickerShowing = false; // True if the popup picker is showing , false if not
	this._inDialog = false; // True if showing within a "dialog", false if not
	this._mainDivId = 'ui-datepicker-div'; // The ID of the main datepicker division
	this._inlineClass = 'ui-datepicker-inline'; // The name of the inline marker class
	this._appendClass = 'ui-datepicker-append'; // The name of the append marker class
	this._triggerClass = 'ui-datepicker-trigger'; // The name of the trigger marker class
	this._dialogClass = 'ui-datepicker-dialog'; // The name of the dialog marker class
	this._disableClass = 'ui-datepicker-disabled'; // The name of the disabled covering marker class
	this._unselectableClass = 'ui-datepicker-unselectable'; // The name of the unselectable cell marker class
	this._currentClass = 'ui-datepicker-current-day'; // The name of the current day marker class
	this._dayOverClass = 'ui-datepicker-days-cell-over'; // The name of the day hover marker class
	this.regional = []; // Available regional settings, indexed by language code
	this.regional[''] = { // Default regional settings
		closeText: 'Done', // Display text for close link
		prevText: 'Prev', // Display text for previous month link
		nextText: 'Next', // Display text for next month link
		currentText: 'Today', // Display text for current month link
		monthNames: ['January','February','March','April','May','June',
			'July','August','September','October','November','December'], // Names of months for drop-down and formatting
		monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], // For formatting
		dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], // For formatting
		dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], // For formatting
		dayNamesMin: ['Su','Mo','Tu','We','Th','Fr','Sa'], // Column headings for days starting at Sunday
		weekHeader: 'Wk', // Column header for week of the year
		dateFormat: 'mm/dd/yy', // See format options on parseDate
		firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ...
		isRTL: false, // True if right-to-left language, false if left-to-right
		showMonthAfterYear: false, // True if the year select precedes month, false for month then year
		yearSuffix: '' // Additional text to append to the year in the month headers
	};
	this._defaults = { // Global defaults for all the date picker instances
		showOn: 'focus', // 'focus' for popup on focus,
			// 'button' for trigger button, or 'both' for either
		showAnim: 'fadeIn', // Name of jQuery animation for popup
		showOptions: {}, // Options for enhanced animations
		defaultDate: null, // Used when field is blank: actual date,
			// +/-number for offset from today, null for today
		appendText: '', // Display text following the input box, e.g. showing the format
		buttonText: '...', // Text for trigger button
		buttonImage: '', // URL for trigger button image
		buttonImageOnly: false, // True if the image appears alone, false if it appears on a button
		hideIfNoPrevNext: false, // True to hide next/previous month links
			// if not applicable, false to just disable them
		navigationAsDateFormat: false, // True if date formatting applied to prev/today/next links
		gotoCurrent: false, // True if today link goes back to current selection instead
		changeMonth: false, // True if month can be selected directly, false if only prev/next
		changeYear: false, // True if year can be selected directly, false if only prev/next
		yearRange: 'c-10:c+10', // Range of years to display in drop-down,
			// either relative to today's year (-nn:+nn), relative to currently displayed year
			// (c-nn:c+nn), absolute (nnnn:nnnn), or a combination of the above (nnnn:-n)
		showOtherMonths: false, // True to show dates in other months, false to leave blank
		selectOtherMonths: false, // True to allow selection of dates in other months, false for unselectable
		showWeek: false, // True to show week of the year, false to not show it
		calculateWeek: this.iso8601Week, // How to calculate the week of the year,
			// takes a Date and returns the number of the week for it
		shortYearCutoff: '+10', // Short year values < this are in the current century,
			// > this are in the previous century,
			// string value starting with '+' for current year + value
		minDate: null, // The earliest selectable date, or null for no limit
		maxDate: null, // The latest selectable date, or null for no limit
		duration: 'fast', // Duration of display/closure
		beforeShowDay: null, // Function that takes a date and returns an array with
			// [0] = true if selectable, false if not, [1] = custom CSS class name(s) or '',
			// [2] = cell title (optional), e.g. $.datepicker.noWeekends
		beforeShow: null, // Function that takes an input field and
			// returns a set of custom settings for the date picker
		onSelect: null, // Define a callback function when a date is selected
		onChangeMonthYear: null, // Define a callback function when the month or year is changed
		onClose: null, // Define a callback function when the datepicker is closed
		numberOfMonths: 1, // Number of months to show at a time
		showCurrentAtPos: 0, // The position in multipe months at which to show the current month (starting at 0)
		stepMonths: 1, // Number of months to step back/forward
		stepBigMonths: 12, // Number of months to step back/forward for the big links
		altField: '', // Selector for an alternate field to store selected dates into
		altFormat: '', // The date format to use for the alternate field
		constrainInput: true, // The input is constrained by the current date format
		showButtonPanel: false, // True to show button panel, false to not show it
		autoSize: false, // True to size the input for the date format, false to leave as is
		disabled: false // The initial disabled state
	};
	$.extend(this._defaults, this.regional['']);
	this.dpDiv = bindHover($('<div id="' + this._mainDivId + '" class="ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>'));
}

$.extend(Datepicker.prototype, {
	/* Class name added to elements to indicate already configured with a date picker. */
	markerClassName: 'hasDatepicker',
	
	//Keep track of the maximum number of rows displayed (see #7043)
	maxRows: 4,

	/* Debug logging (if enabled). */
	log: function () {
		if (this.debug)
			console.log.apply('', arguments);
	},
	
	// TODO rename to "widget" when switching to widget factory
	_widgetDatepicker: function() {
		return this.dpDiv;
	},

	/* Override the default settings for all instances of the date picker.
	   @param  settings  object - the new settings to use as defaults (anonymous object)
	   @return the manager object */
	setDefaults: function(settings) {
		extendRemove(this._defaults, settings || {});
		return this;
	},

	/* Attach the date picker to a jQuery selection.
	   @param  target    element - the target input field or division or span
	   @param  settings  object - the new settings to use for this date picker instance (anonymous) */
	_attachDatepicker: function(target, settings) {
		// check for settings on the control itself - in namespace 'date:'
		var inlineSettings = null;
		for (var attrName in this._defaults) {
			var attrValue = target.getAttribute('date:' + attrName);
			if (attrValue) {
				inlineSettings = inlineSettings || {};
				try {
					inlineSettings[attrName] = eval(attrValue);
				} catch (err) {
					inlineSettings[attrName] = attrValue;
				}
			}
		}
		var nodeName = target.nodeName.toLowerCase();
		var inline = (nodeName == 'div' || nodeName == 'span');
		if (!target.id) {
			this.uuid += 1;
			target.id = 'dp' + this.uuid;
		}
		var inst = this._newInst($(target), inline);
		inst.settings = $.extend({}, settings || {}, inlineSettings || {});
		if (nodeName == 'input') {
			this._connectDatepicker(target, inst);
		} else if (inline) {
			this._inlineDatepicker(target, inst);
		}
	},

	/* Create a new instance object. */
	_newInst: function(target, inline) {
		var id = target[0].id.replace(/([^A-Za-z0-9_-])/g, '\\\\$1'); // escape jQuery meta chars
		return {id: id, input: target, // associated target
			selectedDay: 0, selectedMonth: 0, selectedYear: 0, // current selection
			drawMonth: 0, drawYear: 0, // month being drawn
			inline: inline, // is datepicker inline or not
			dpDiv: (!inline ? this.dpDiv : // presentation div
			bindHover($('<div class="' + this._inlineClass + ' ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>')))};
	},

	/* Attach the date picker to an input field. */
	_connectDatepicker: function(target, inst) {
		var input = $(target);
		inst.append = $([]);
		inst.trigger = $([]);
		if (input.hasClass(this.markerClassName))
			return;
		this._attachments(input, inst);
		input.addClass(this.markerClassName).keydown(this._doKeyDown).
			keypress(this._doKeyPress).keyup(this._doKeyUp).
			bind("setData.datepicker", function(event, key, value) {
				inst.settings[key] = value;
			}).bind("getData.datepicker", function(event, key) {
				return this._get(inst, key);
			});
		this._autoSize(inst);
		$.data(target, PROP_NAME, inst);
		//If disabled option is true, disable the datepicker once it has been attached to the input (see ticket #5665)
		if( inst.settings.disabled ) {
			this._disableDatepicker( target );
		}
	},

	/* Make attachments based on settings. */
	_attachments: function(input, inst) {
		var appendText = this._get(inst, 'appendText');
		var isRTL = this._get(inst, 'isRTL');
		if (inst.append)
			inst.append.remove();
		if (appendText) {
			inst.append = $('<span class="' + this._appendClass + '">' + appendText + '</span>');
			input[isRTL ? 'before' : 'after'](inst.append);
		}
		input.unbind('focus', this._showDatepicker);
		if (inst.trigger)
			inst.trigger.remove();
		var showOn = this._get(inst, 'showOn');
		if (showOn == 'focus' || showOn == 'both') // pop-up date picker when in the marked field
			input.focus(this._showDatepicker);
		if (showOn == 'button' || showOn == 'both') { // pop-up date picker when button clicked
			var buttonText = this._get(inst, 'buttonText');
			var buttonImage = this._get(inst, 'buttonImage');
			inst.trigger = $(this._get(inst, 'buttonImageOnly') ?
				$('<img/>').addClass(this._triggerClass).
					attr({ src: buttonImage, alt: buttonText, title: buttonText }) :
				$('<button type="button"></button>').addClass(this._triggerClass).
					html(buttonImage == '' ? buttonText : $('<img/>').attr(
					{ src:buttonImage, alt:buttonText, title:buttonText })));
			input[isRTL ? 'before' : 'after'](inst.trigger);
			inst.trigger.click(function() {
				if ($.datepicker._datepickerShowing && $.datepicker._lastInput == input[0])
					$.datepicker._hideDatepicker();
				else if ($.datepicker._datepickerShowing && $.datepicker._lastInput != input[0]) {
					$.datepicker._hideDatepicker(); 
					$.datepicker._showDatepicker(input[0]);
				} else
					$.datepicker._showDatepicker(input[0]);
				return false;
			});
		}
	},

	/* Apply the maximum length for the date format. */
	_autoSize: function(inst) {
		if (this._get(inst, 'autoSize') && !inst.inline) {
			var date = new Date(2009, 12 - 1, 20); // Ensure double digits
			var dateFormat = this._get(inst, 'dateFormat');
			if (dateFormat.match(/[DM]/)) {
				var findMax = function(names) {
					var max = 0;
					var maxI = 0;
					for (var i = 0; i < names.length; i++) {
						if (names[i].length > max) {
							max = names[i].length;
							maxI = i;
						}
					}
					return maxI;
				};
				date.setMonth(findMax(this._get(inst, (dateFormat.match(/MM/) ?
					'monthNames' : 'monthNamesShort'))));
				date.setDate(findMax(this._get(inst, (dateFormat.match(/DD/) ?
					'dayNames' : 'dayNamesShort'))) + 20 - date.getDay());
			}
			inst.input.attr('size', this._formatDate(inst, date).length);
		}
	},

	/* Attach an inline date picker to a div. */
	_inlineDatepicker: function(target, inst) {
		var divSpan = $(target);
		if (divSpan.hasClass(this.markerClassName))
			return;
		divSpan.addClass(this.markerClassName).append(inst.dpDiv).
			bind("setData.datepicker", function(event, key, value){
				inst.settings[key] = value;
			}).bind("getData.datepicker", function(event, key){
				return this._get(inst, key);
			});
		$.data(target, PROP_NAME, inst);
		this._setDate(inst, this._getDefaultDate(inst), true);
		this._updateDatepicker(inst);
		this._updateAlternate(inst);
		//If disabled option is true, disable the datepicker before showing it (see ticket #5665)
		if( inst.settings.disabled ) {
			this._disableDatepicker( target );
		}
		// Set display:block in place of inst.dpDiv.show() which won't work on disconnected elements
		// http://bugs.jqueryui.com/ticket/7552 - A Datepicker created on a detached div has zero height
		inst.dpDiv.css( "display", "block" );
	},

	/* Pop-up the date picker in a "dialog" box.
	   @param  input     element - ignored
	   @param  date      string or Date - the initial date to display
	   @param  onSelect  function - the function to call when a date is selected
	   @param  settings  object - update the dialog date picker instance's settings (anonymous object)
	   @param  pos       int[2] - coordinates for the dialog's position within the screen or
	                     event - with x/y coordinates or
	                     leave empty for default (screen centre)
	   @return the manager object */
	_dialogDatepicker: function(input, date, onSelect, settings, pos) {
		var inst = this._dialogInst; // internal instance
		if (!inst) {
			this.uuid += 1;
			var id = 'dp' + this.uuid;
			this._dialogInput = $('<input type="text" id="' + id +
				'" style="position: absolute; top: -100px; width: 0px;"/>');
			this._dialogInput.keydown(this._doKeyDown);
			$('body').append(this._dialogInput);
			inst = this._dialogInst = this._newInst(this._dialogInput, false);
			inst.settings = {};
			$.data(this._dialogInput[0], PROP_NAME, inst);
		}
		extendRemove(inst.settings, settings || {});
		date = (date && date.constructor == Date ? this._formatDate(inst, date) : date);
		this._dialogInput.val(date);

		this._pos = (pos ? (pos.length ? pos : [pos.pageX, pos.pageY]) : null);
		if (!this._pos) {
			var browserWidth = document.documentElement.clientWidth;
			var browserHeight = document.documentElement.clientHeight;
			var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;
			var scrollY = document.documentElement.scrollTop || document.body.scrollTop;
			this._pos = // should use actual width/height below
				[(browserWidth / 2) - 100 + scrollX, (browserHeight / 2) - 150 + scrollY];
		}

		// move input on screen for focus, but hidden behind dialog
		this._dialogInput.css('left', (this._pos[0] + 20) + 'px').css('top', this._pos[1] + 'px');
		inst.settings.onSelect = onSelect;
		this._inDialog = true;
		this.dpDiv.addClass(this._dialogClass);
		this._showDatepicker(this._dialogInput[0]);
		if ($.blockUI)
			$.blockUI(this.dpDiv);
		$.data(this._dialogInput[0], PROP_NAME, inst);
		return this;
	},

	/* Detach a datepicker from its control.
	   @param  target    element - the target input field or division or span */
	_destroyDatepicker: function(target) {
		var $target = $(target);
		var inst = $.data(target, PROP_NAME);
		if (!$target.hasClass(this.markerClassName)) {
			return;
		}
		var nodeName = target.nodeName.toLowerCase();
		$.removeData(target, PROP_NAME);
		if (nodeName == 'input') {
			inst.append.remove();
			inst.trigger.remove();
			$target.removeClass(this.markerClassName).
				unbind('focus', this._showDatepicker).
				unbind('keydown', this._doKeyDown).
				unbind('keypress', this._doKeyPress).
				unbind('keyup', this._doKeyUp);
		} else if (nodeName == 'div' || nodeName == 'span')
			$target.removeClass(this.markerClassName).empty();
	},

	/* Enable the date picker to a jQuery selection.
	   @param  target    element - the target input field or division or span */
	_enableDatepicker: function(target) {
		var $target = $(target);
		var inst = $.data(target, PROP_NAME);
		if (!$target.hasClass(this.markerClassName)) {
			return;
		}
		var nodeName = target.nodeName.toLowerCase();
		if (nodeName == 'input') {
			target.disabled = false;
			inst.trigger.filter('button').
				each(function() { this.disabled = false; }).end().
				filter('img').css({opacity: '1.0', cursor: ''});
		}
		else if (nodeName == 'div' || nodeName == 'span') {
			var inline = $target.children('.' + this._inlineClass);
			inline.children().removeClass('ui-state-disabled');
			inline.find("select.ui-datepicker-month, select.ui-datepicker-year").
				removeAttr("disabled");
		}
		this._disabledInputs = $.map(this._disabledInputs,
			function(value) { return (value == target ? null : value); }); // delete entry
	},

	/* Disable the date picker to a jQuery selection.
	   @param  target    element - the target input field or division or span */
	_disableDatepicker: function(target) {
		var $target = $(target);
		var inst = $.data(target, PROP_NAME);
		if (!$target.hasClass(this.markerClassName)) {
			return;
		}
		var nodeName = target.nodeName.toLowerCase();
		if (nodeName == 'input') {
			target.disabled = true;
			inst.trigger.filter('button').
				each(function() { this.disabled = true; }).end().
				filter('img').css({opacity: '0.5', cursor: 'default'});
		}
		else if (nodeName == 'div' || nodeName == 'span') {
			var inline = $target.children('.' + this._inlineClass);
			inline.children().addClass('ui-state-disabled');
			inline.find("select.ui-datepicker-month, select.ui-datepicker-year").
				attr("disabled", "disabled");
		}
		this._disabledInputs = $.map(this._disabledInputs,
			function(value) { return (value == target ? null : value); }); // delete entry
		this._disabledInputs[this._disabledInputs.length] = target;
	},

	/* Is the first field in a jQuery collection disabled as a datepicker?
	   @param  target    element - the target input field or division or span
	   @return boolean - true if disabled, false if enabled */
	_isDisabledDatepicker: function(target) {
		if (!target) {
			return false;
		}
		for (var i = 0; i < this._disabledInputs.length; i++) {
			if (this._disabledInputs[i] == target)
				return true;
		}
		return false;
	},

	/* Retrieve the instance data for the target control.
	   @param  target  element - the target input field or division or span
	   @return  object - the associated instance data
	   @throws  error if a jQuery problem getting data */
	_getInst: function(target) {
		try {
			return $.data(target, PROP_NAME);
		}
		catch (err) {
			throw 'Missing instance data for this datepicker';
		}
	},

	/* Update or retrieve the settings for a date picker attached to an input field or division.
	   @param  target  element - the target input field or division or span
	   @param  name    object - the new settings to update or
	                   string - the name of the setting to change or retrieve,
	                   when retrieving also 'all' for all instance settings or
	                   'defaults' for all global defaults
	   @param  value   any - the new value for the setting
	                   (omit if above is an object or to retrieve a value) */
	_optionDatepicker: function(target, name, value) {
		var inst = this._getInst(target);
		if (arguments.length == 2 && typeof name == 'string') {
			return (name == 'defaults' ? $.extend({}, $.datepicker._defaults) :
				(inst ? (name == 'all' ? $.extend({}, inst.settings) :
				this._get(inst, name)) : null));
		}
		var settings = name || {};
		if (typeof name == 'string') {
			settings = {};
			settings[name] = value;
		}
		if (inst) {
			if (this._curInst == inst) {
				this._hideDatepicker();
			}
			var date = this._getDateDatepicker(target, true);
			var minDate = this._getMinMaxDate(inst, 'min');
			var maxDate = this._getMinMaxDate(inst, 'max');
			extendRemove(inst.settings, settings);
			// reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided
			if (minDate !== null && settings['dateFormat'] !== undefined && settings['minDate'] === undefined)
				inst.settings.minDate = this._formatDate(inst, minDate);
			if (maxDate !== null && settings['dateFormat'] !== undefined && settings['maxDate'] === undefined)
				inst.settings.maxDate = this._formatDate(inst, maxDate);
			this._attachments($(target), inst);
			this._autoSize(inst);
			this._setDate(inst, date);
			this._updateAlternate(inst);
			this._updateDatepicker(inst);
		}
	},

	// change method deprecated
	_changeDatepicker: function(target, name, value) {
		this._optionDatepicker(target, name, value);
	},

	/* Redraw the date picker attached to an input field or division.
	   @param  target  element - the target input field or division or span */
	_refreshDatepicker: function(target) {
		var inst = this._getInst(target);
		if (inst) {
			this._updateDatepicker(inst);
		}
	},

	/* Set the dates for a jQuery selection.
	   @param  target   element - the target input field or division or span
	   @param  date     Date - the new date */
	_setDateDatepicker: function(target, date) {
		var inst = this._getInst(target);
		if (inst) {
			this._setDate(inst, date);
			this._updateDatepicker(inst);
			this._updateAlternate(inst);
		}
	},

	/* Get the date(s) for the first entry in a jQuery selection.
	   @param  target     element - the target input field or division or span
	   @param  noDefault  boolean - true if no default date is to be used
	   @return Date - the current date */
	_getDateDatepicker: function(target, noDefault) {
		var inst = this._getInst(target);
		if (inst && !inst.inline)
			this._setDateFromField(inst, noDefault);
		return (inst ? this._getDate(inst) : null);
	},

	/* Handle keystrokes. */
	_doKeyDown: function(event) {
		var inst = $.datepicker._getInst(event.target);
		var handled = true;
		var isRTL = inst.dpDiv.is('.ui-datepicker-rtl');
		inst._keyEvent = true;
		if ($.datepicker._datepickerShowing)
			switch (event.keyCode) {
				case 9: $.datepicker._hideDatepicker();
						handled = false;
						break; // hide on tab out
				case 13: var sel = $('td.' + $.datepicker._dayOverClass + ':not(.' + 
									$.datepicker._currentClass + ')', inst.dpDiv);
						if (sel[0])
							$.datepicker._selectDay(event.target, inst.selectedMonth, inst.selectedYear, sel[0]);
							var onSelect = $.datepicker._get(inst, 'onSelect');
							if (onSelect) {
								var dateStr = $.datepicker._formatDate(inst);

								// trigger custom callback
								onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]);
							}
						else
							$.datepicker._hideDatepicker();
						return false; // don't submit the form
						break; // select the value on enter
				case 27: $.datepicker._hideDatepicker();
						break; // hide on escape
				case 33: $.datepicker._adjustDate(event.target, (event.ctrlKey ?
							-$.datepicker._get(inst, 'stepBigMonths') :
							-$.datepicker._get(inst, 'stepMonths')), 'M');
						break; // previous month/year on page up/+ ctrl
				case 34: $.datepicker._adjustDate(event.target, (event.ctrlKey ?
							+$.datepicker._get(inst, 'stepBigMonths') :
							+$.datepicker._get(inst, 'stepMonths')), 'M');
						break; // next month/year on page down/+ ctrl
				case 35: if (event.ctrlKey || event.metaKey) $.datepicker._clearDate(event.target);
						handled = event.ctrlKey || event.metaKey;
						break; // clear on ctrl or command +end
				case 36: if (event.ctrlKey || event.metaKey) $.datepicker._gotoToday(event.target);
						handled = event.ctrlKey || event.metaKey;
						break; // current on ctrl or command +home
				case 37: if (event.ctrlKey || event.metaKey) $.datepicker._adjustDate(event.target, (isRTL ? +1 : -1), 'D');
						handled = event.ctrlKey || event.metaKey;
						// -1 day on ctrl or command +left
						if (event.originalEvent.altKey) $.datepicker._adjustDate(event.target, (event.ctrlKey ?
									-$.datepicker._get(inst, 'stepBigMonths') :
									-$.datepicker._get(inst, 'stepMonths')), 'M');
						// next month/year on alt +left on Mac
						break;
				case 38: if (event.ctrlKey || event.metaKey) $.datepicker._adjustDate(event.target, -7, 'D');
						handled = event.ctrlKey || event.metaKey;
						break; // -1 week on ctrl or command +up
				case 39: if (event.ctrlKey || event.metaKey) $.datepicker._adjustDate(event.target, (isRTL ? -1 : +1), 'D');
						handled = event.ctrlKey || event.metaKey;
						// +1 day on ctrl or command +right
						if (event.originalEvent.altKey) $.datepicker._adjustDate(event.target, (event.ctrlKey ?
									+$.datepicker._get(inst, 'stepBigMonths') :
									+$.datepicker._get(inst, 'stepMonths')), 'M');
						// next month/year on alt +right
						break;
				case 40: if (event.ctrlKey || event.metaKey) $.datepicker._adjustDate(event.target, +7, 'D');
						handled = event.ctrlKey || event.metaKey;
						break; // +1 week on ctrl or command +down
				default: handled = false;
			}
		else if (event.keyCode == 36 && event.ctrlKey) // display the date picker on ctrl+home
			$.datepicker._showDatepicker(this);
		else {
			handled = false;
		}
		if (handled) {
			event.preventDefault();
			event.stopPropagation();
		}
	},

	/* Filter entered characters - based on date format. */
	_doKeyPress: function(event) {
		var inst = $.datepicker._getInst(event.target);
		if ($.datepicker._get(inst, 'constrainInput')) {
			var chars = $.datepicker._possibleChars($.datepicker._get(inst, 'dateFormat'));
			var chr = String.fromCharCode(event.charCode == undefined ? event.keyCode : event.charCode);
			return event.ctrlKey || event.metaKey || (chr < ' ' || !chars || chars.indexOf(chr) > -1);
		}
	},

	/* Synchronise manual entry and field/alternate field. */
	_doKeyUp: function(event) {
		var inst = $.datepicker._getInst(event.target);
		if (inst.input.val() != inst.lastVal) {
			try {
				var date = $.datepicker.parseDate($.datepicker._get(inst, 'dateFormat'),
					(inst.input ? inst.input.val() : null),
					$.datepicker._getFormatConfig(inst));
				if (date) { // only if valid
					$.datepicker._setDateFromField(inst);
					$.datepicker._updateAlternate(inst);
					$.datepicker._updateDatepicker(inst);
				}
			}
			catch (err) {
				$.datepicker.log(err);
			}
		}
		return true;
	},

	/* Pop-up the date picker for a given input field.
       If false returned from beforeShow event handler do not show. 
	   @param  input  element - the input field attached to the date picker or
	                  event - if triggered by focus */
	_showDatepicker: function(input) {
		input = input.target || input;
		if (input.nodeName.toLowerCase() != 'input') // find from button/image trigger
			input = $('input', input.parentNode)[0];
		if ($.datepicker._isDisabledDatepicker(input) || $.datepicker._lastInput == input) // already here
			return;
		var inst = $.datepicker._getInst(input);
		if ($.datepicker._curInst && $.datepicker._curInst != inst) {
			$.datepicker._curInst.dpDiv.stop(true, true);
			if ( inst && $.datepicker._datepickerShowing ) {
				$.datepicker._hideDatepicker( $.datepicker._curInst.input[0] );
			}
		}
		var beforeShow = $.datepicker._get(inst, 'beforeShow');
		var beforeShowSettings = beforeShow ? beforeShow.apply(input, [input, inst]) : {};
		if(beforeShowSettings === false){
            //false
			return;
		}
		extendRemove(inst.settings, beforeShowSettings);
		inst.lastVal = null;
		$.datepicker._lastInput = input;
		$.datepicker._setDateFromField(inst);
		if ($.datepicker._inDialog) // hide cursor
			input.value = '';
		if (!$.datepicker._pos) { // position below input
			$.datepicker._pos = $.datepicker._findPos(input);
			$.datepicker._pos[1] += input.offsetHeight; // add the height
		}
		var isFixed = false;
		$(input).parents().each(function() {
			isFixed |= $(this).css('position') == 'fixed';
			return !isFixed;
		});
		if (isFixed && $.browser.opera) { // correction for Opera when fixed and scrolled
			$.datepicker._pos[0] -= document.documentElement.scrollLeft;
			$.datepicker._pos[1] -= document.documentElement.scrollTop;
		}
		var offset = {left: $.datepicker._pos[0], top: $.datepicker._pos[1]};
		$.datepicker._pos = null;
		//to avoid flashes on Firefox
		inst.dpDiv.empty();
		// determine sizing offscreen
		inst.dpDiv.css({position: 'absolute', display: 'block', top: '-1000px'});
		$.datepicker._updateDatepicker(inst);
		// fix width for dynamic number of date pickers
		// and adjust position before showing
		offset = $.datepicker._checkOffset(inst, offset, isFixed);
		inst.dpDiv.css({position: ($.datepicker._inDialog && $.blockUI ?
			'static' : (isFixed ? 'fixed' : 'absolute')), display: 'none',
			left: offset.left + 'px', top: offset.top + 'px'});
		if (!inst.inline) {
			var showAnim = $.datepicker._get(inst, 'showAnim');
			var duration = $.datepicker._get(inst, 'duration');
			var postProcess = function() {
				var cover = inst.dpDiv.find('iframe.ui-datepicker-cover'); // IE6- only
				if( !! cover.length ){
					var borders = $.datepicker._getBorders(inst.dpDiv);
					cover.css({left: -borders[0], top: -borders[1],
						width: inst.dpDiv.outerWidth(), height: inst.dpDiv.outerHeight()});
				}
			};
			inst.dpDiv.zIndex($(input).zIndex()+1);
			$.datepicker._datepickerShowing = true;
			if ($.effects && $.effects[showAnim])
				inst.dpDiv.show(showAnim, $.datepicker._get(inst, 'showOptions'), duration, postProcess);
			else
				inst.dpDiv[showAnim || 'show']((showAnim ? duration : null), postProcess);
			if (!showAnim || !duration)
				postProcess();
			if (inst.input.is(':visible') && !inst.input.is(':disabled'))
				inst.input.focus();
			$.datepicker._curInst = inst;
		}
	},

	/* Generate the date picker content. */
	_updateDatepicker: function(inst) {
		var self = this;
		self.maxRows = 4; //Reset the max number of rows being displayed (see #7043)
		var borders = $.datepicker._getBorders(inst.dpDiv);
		instActive = inst; // for delegate hover events
		inst.dpDiv.empty().append(this._generateHTML(inst));
		this._attachHandlers(inst);
		var cover = inst.dpDiv.find('iframe.ui-datepicker-cover'); // IE6- only
		if( !!cover.length ){ //avoid call to outerXXXX() when not in IE6
			cover.css({left: -borders[0], top: -borders[1], width: inst.dpDiv.outerWidth(), height: inst.dpDiv.outerHeight()})
		}
		inst.dpDiv.find('.' + this._dayOverClass + ' a').mouseover();
		var numMonths = this._getNumberOfMonths(inst);
		var cols = numMonths[1];
		var width = 17;
		inst.dpDiv.removeClass('ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4').width('');
		if (cols > 1)
			inst.dpDiv.addClass('ui-datepicker-multi-' + cols).css('width', (width * cols) + 'em');
		inst.dpDiv[(numMonths[0] != 1 || numMonths[1] != 1 ? 'add' : 'remove') +
			'Class']('ui-datepicker-multi');
		inst.dpDiv[(this._get(inst, 'isRTL') ? 'add' : 'remove') +
			'Class']('ui-datepicker-rtl');
		if (inst == $.datepicker._curInst && $.datepicker._datepickerShowing && inst.input &&
				// #6694 - don't focus the input if it's already focused
				// this breaks the change event in IE
				inst.input.is(':visible') && !inst.input.is(':disabled') && inst.input[0] != document.activeElement)
			inst.input.focus();
		// deffered render of the years select (to avoid flashes on Firefox) 
		if( inst.yearshtml ){
			var origyearshtml = inst.yearshtml;
			setTimeout(function(){
				//assure that inst.yearshtml didn't change.
				if( origyearshtml === inst.yearshtml && inst.yearshtml ){
					inst.dpDiv.find('select.ui-datepicker-year:first').replaceWith(inst.yearshtml);
				}
				origyearshtml = inst.yearshtml = null;
			}, 0);
		}
	},

	/* Retrieve the size of left and top borders for an element.
	   @param  elem  (jQuery object) the element of interest
	   @return  (number[2]) the left and top borders */
	_getBorders: function(elem) {
		var convert = function(value) {
			return {thin: 1, medium: 2, thick: 3}[value] || value;
		};
		return [parseFloat(convert(elem.css('border-left-width'))),
			parseFloat(convert(elem.css('border-top-width')))];
	},

	/* Check positioning to remain on screen. */
	_checkOffset: function(inst, offset, isFixed) {
		var dpWidth = inst.dpDiv.outerWidth();
		var dpHeight = inst.dpDiv.outerHeight();
		var inputWidth = inst.input ? inst.input.outerWidth() : 0;
		var inputHeight = inst.input ? inst.input.outerHeight() : 0;
		var viewWidth = document.documentElement.clientWidth + (isFixed ? 0 : $(document).scrollLeft());
		var viewHeight = document.documentElement.clientHeight + (isFixed ? 0 : $(document).scrollTop());

		offset.left -= (this._get(inst, 'isRTL') ? (dpWidth - inputWidth) : 0);
		offset.left -= (isFixed && offset.left == inst.input.offset().left) ? $(document).scrollLeft() : 0;
		offset.top -= (isFixed && offset.top == (inst.input.offset().top + inputHeight)) ? $(document).scrollTop() : 0;

		// now check if datepicker is showing outside window viewport - move to a better place if so.
		offset.left -= Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ?
			Math.abs(offset.left + dpWidth - viewWidth) : 0);
		offset.top -= Math.min(offset.top, (offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ?
			Math.abs(dpHeight + inputHeight) : 0);

		return offset;
	},

	/* Find an object's position on the screen. */
	_findPos: function(obj) {
		var inst = this._getInst(obj);
		var isRTL = this._get(inst, 'isRTL');
        while (obj && (obj.type == 'hidden' || obj.nodeType != 1 || $.expr.filters.hidden(obj))) {
            obj = obj[isRTL ? 'previousSibling' : 'nextSibling'];
        }
        var position = $(obj).offset();
	    return [position.left, position.top];
	},

	/* Hide the date picker from view.
	   @param  input  element - the input field attached to the date picker */
	_hideDatepicker: function(input) {
		var inst = this._curInst;
		if (!inst || (input && inst != $.data(input, PROP_NAME)))
			return;
		if (this._datepickerShowing) {
			var showAnim = this._get(inst, 'showAnim');
			var duration = this._get(inst, 'duration');
			var postProcess = function() {
				$.datepicker._tidyDialog(inst);
			};
			if ($.effects && $.effects[showAnim])
				inst.dpDiv.hide(showAnim, $.datepicker._get(inst, 'showOptions'), duration, postProcess);
			else
				inst.dpDiv[(showAnim == 'slideDown' ? 'slideUp' :
					(showAnim == 'fadeIn' ? 'fadeOut' : 'hide'))]((showAnim ? duration : null), postProcess);
			if (!showAnim)
				postProcess();
			this._datepickerShowing = false;
			var onClose = this._get(inst, 'onClose');
			if (onClose)
				onClose.apply((inst.input ? inst.input[0] : null),
					[(inst.input ? inst.input.val() : ''), inst]);
			this._lastInput = null;
			if (this._inDialog) {
				this._dialogInput.css({ position: 'absolute', left: '0', top: '-100px' });
				if ($.blockUI) {
					$.unblockUI();
					$('body').append(this.dpDiv);
				}
			}
			this._inDialog = false;
		}
	},

	/* Tidy up after a dialog display. */
	_tidyDialog: function(inst) {
		inst.dpDiv.removeClass(this._dialogClass).unbind('.ui-datepicker-calendar');
	},

	/* Close date picker if clicked elsewhere. */
	_checkExternalClick: function(event) {
		if (!$.datepicker._curInst)
			return;

		var $target = $(event.target),
			inst = $.datepicker._getInst($target[0]);

		if ( ( ( $target[0].id != $.datepicker._mainDivId &&
				$target.parents('#' + $.datepicker._mainDivId).length == 0 &&
				!$target.hasClass($.datepicker.markerClassName) &&
				!$target.closest("." + $.datepicker._triggerClass).length &&
				$.datepicker._datepickerShowing && !($.datepicker._inDialog && $.blockUI) ) ) ||
			( $target.hasClass($.datepicker.markerClassName) && $.datepicker._curInst != inst ) )
			$.datepicker._hideDatepicker();
	},

	/* Adjust one of the date sub-fields. */
	_adjustDate: function(id, offset, period) {
		var target = $(id);
		var inst = this._getInst(target[0]);
		if (this._isDisabledDatepicker(target[0])) {
			return;
		}
		this._adjustInstDate(inst, offset +
			(period == 'M' ? this._get(inst, 'showCurrentAtPos') : 0), // undo positioning
			period);
		this._updateDatepicker(inst);
	},

	/* Action for current link. */
	_gotoToday: function(id) {
		var target = $(id);
		var inst = this._getInst(target[0]);
		if (this._get(inst, 'gotoCurrent') && inst.currentDay) {
			inst.selectedDay = inst.currentDay;
			inst.drawMonth = inst.selectedMonth = inst.currentMonth;
			inst.drawYear = inst.selectedYear = inst.currentYear;
		}
		else {
			var date = new Date();
			inst.selectedDay = date.getDate();
			inst.drawMonth = inst.selectedMonth = date.getMonth();
			inst.drawYear = inst.selectedYear = date.getFullYear();
		}
		this._notifyChange(inst);
		this._adjustDate(target);
	},

	/* Action for selecting a new month/year. */
	_selectMonthYear: function(id, select, period) {
		var target = $(id);
		var inst = this._getInst(target[0]);
		inst['selected' + (period == 'M' ? 'Month' : 'Year')] =
		inst['draw' + (period == 'M' ? 'Month' : 'Year')] =
			parseInt(select.options[select.selectedIndex].value,10);
		this._notifyChange(inst);
		this._adjustDate(target);
	},

	/* Action for selecting a day. */
	_selectDay: function(id, month, year, td) {
		var target = $(id);
		if ($(td).hasClass(this._unselectableClass) || this._isDisabledDatepicker(target[0])) {
			return;
		}
		var inst = this._getInst(target[0]);
		inst.selectedDay = inst.currentDay = $('a', td).html();
		inst.selectedMonth = inst.currentMonth = month;
		inst.selectedYear = inst.currentYear = year;
		this._selectDate(id, this._formatDate(inst,
			inst.currentDay, inst.currentMonth, inst.currentYear));
	},

	/* Erase the input field and hide the date picker. */
	_clearDate: function(id) {
		var target = $(id);
		var inst = this._getInst(target[0]);
		this._selectDate(target, '');
	},

	/* Update the input field with the selected date. */
	_selectDate: function(id, dateStr) {
		var target = $(id);
		var inst = this._getInst(target[0]);
		dateStr = (dateStr != null ? dateStr : this._formatDate(inst));
		if (inst.input)
			inst.input.val(dateStr);
		this._updateAlternate(inst);
		var onSelect = this._get(inst, 'onSelect');
		if (onSelect)
			onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]);  // trigger custom callback
		else if (inst.input)
			inst.input.trigger('change'); // fire the change event
		if (inst.inline)
			this._updateDatepicker(inst);
		else {
			this._hideDatepicker();
			this._lastInput = inst.input[0];
			if (typeof(inst.input[0]) != 'object')
				inst.input.focus(); // restore focus
			this._lastInput = null;
		}
	},

	/* Update any alternate field to synchronise with the main field. */
	_updateAlternate: function(inst) {
		var altField = this._get(inst, 'altField');
		if (altField) { // update alternate field too
			var altFormat = this._get(inst, 'altFormat') || this._get(inst, 'dateFormat');
			var date = this._getDate(inst);
			var dateStr = this.formatDate(altFormat, date, this._getFormatConfig(inst));
			$(altField).each(function() { $(this).val(dateStr); });
		}
	},

	/* Set as beforeShowDay function to prevent selection of weekends.
	   @param  date  Date - the date to customise
	   @return [boolean, string] - is this date selectable?, what is its CSS class? */
	noWeekends: function(date) {
		var day = date.getDay();
		return [(day > 0 && day < 6), ''];
	},

	/* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition.
	   @param  date  Date - the date to get the week for
	   @return  number - the number of the week within the year that contains this date */
	iso8601Week: function(date) {
		var checkDate = new Date(date.getTime());
		// Find Thursday of this week starting on Monday
		checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7));
		var time = checkDate.getTime();
		checkDate.setMonth(0); // Compare with Jan 1
		checkDate.setDate(1);
		return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
	},

	/* Parse a string value into a date object.
	   See formatDate below for the possible formats.

	   @param  format    string - the expected format of the date
	   @param  value     string - the date in the above format
	   @param  settings  Object - attributes include:
	                     shortYearCutoff  number - the cutoff year for determining the century (optional)
	                     dayNamesShort    string[7] - abbreviated names of the days from Sunday (optional)
	                     dayNames         string[7] - names of the days from Sunday (optional)
	                     monthNamesShort  string[12] - abbreviated names of the months (optional)
	                     monthNames       string[12] - names of the months (optional)
	   @return  Date - the extracted date value or null if value is blank */
	parseDate: function (format, value, settings) {
		if (format == null || value == null)
			throw 'Invalid arguments';
		value = (typeof value == 'object' ? value.toString() : value + '');
		if (value == '')
			return null;
		var shortYearCutoff = (settings ? settings.shortYearCutoff : null) || this._defaults.shortYearCutoff;
		shortYearCutoff = (typeof shortYearCutoff != 'string' ? shortYearCutoff :
				new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10));
		var dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort;
		var dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames;
		var monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort;
		var monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames;
		var year = -1;
		var month = -1;
		var day = -1;
		var doy = -1;
		var literal = false;
		// Check whether a format character is doubled
		var lookAhead = function(match) {
			var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match);
			if (matches)
				iFormat++;
			return matches;
		};
		// Extract a number from the string value
		var getNumber = function(match) {
			var isDoubled = lookAhead(match);
			var size = (match == '@' ? 14 : (match == '!' ? 20 :
				(match == 'y' && isDoubled ? 4 : (match == 'o' ? 3 : 2))));
			var digits = new RegExp('^\\d{1,' + size + '}');
			var num = value.substring(iValue).match(digits);
			if (!num)
				throw 'Missing number at position ' + iValue;
			iValue += num[0].length;
			return parseInt(num[0], 10);
		};
		// Extract a name from the string value and convert to an index
		var getName = function(match, shortNames, longNames) {
			var names = $.map(lookAhead(match) ? longNames : shortNames, function (v, k) {
				return [ [k, v] ];
			}).sort(function (a, b) {
				return -(a[1].length - b[1].length);
			});
			var index = -1;
			$.each(names, function (i, pair) {
				var name = pair[1];
				if (value.substr(iValue, name.length).toLowerCase() == name.toLowerCase()) {
					index = pair[0];
					iValue += name.length;
					return false;
				}
			});
			if (index != -1)
				return index + 1;
			else
				throw 'Unknown name at position ' + iValue;
		};
		// Confirm that a literal character matches the string value
		var checkLiteral = function() {
			if (value.charAt(iValue) != format.charAt(iFormat))
				throw 'Unexpected literal at position ' + iValue;
			iValue++;
		};
		var iValue = 0;
		for (var iFormat = 0; iFormat < format.length; iFormat++) {
			if (literal)
				if (format.charAt(iFormat) == "'" && !lookAhead("'"))
					literal = false;
				else
					checkLiteral();
			else
				switch (format.charAt(iFormat)) {
					case 'd':
						day = getNumber('d');
						break;
					case 'D':
						getName('D', dayNamesShort, dayNames);
						break;
					case 'o':
						doy = getNumber('o');
						break;
					case 'm':
						month = getNumber('m');
						break;
					case 'M':
						month = getName('M', monthNamesShort, monthNames);
						break;
					case 'y':
						year = getNumber('y');
						break;
					case '@':
						var date = new Date(getNumber('@'));
						year = date.getFullYear();
						month = date.getMonth() + 1;
						day = date.getDate();
						break;
					case '!':
						var date = new Date((getNumber('!') - this._ticksTo1970) / 10000);
						year = date.getFullYear();
						month = date.getMonth() + 1;
						day = date.getDate();
						break;
					case "'":
						if (lookAhead("'"))
							checkLiteral();
						else
							literal = true;
						break;
					default:
						checkLiteral();
				}
		}
		if (iValue < value.length){
			throw "Extra/unparsed characters found in date: " + value.substring(iValue);
		}
		if (year == -1)
			year = new Date().getFullYear();
		else if (year < 100)
			year += new Date().getFullYear() - new Date().getFullYear() % 100 +
				(year <= shortYearCutoff ? 0 : -100);
		if (doy > -1) {
			month = 1;
			day = doy;
			do {
				var dim = this._getDaysInMonth(year, month - 1);
				if (day <= dim)
					break;
				month++;
				day -= dim;
			} while (true);
		}
		var date = this._daylightSavingAdjust(new Date(year, month - 1, day));
		if (date.getFullYear() != year || date.getMonth() + 1 != month || date.getDate() != day)
			throw 'Invalid date'; // E.g. 31/02/00
		return date;
	},

	/* Standard date formats. */
	ATOM: 'yy-mm-dd', // RFC 3339 (ISO 8601)
	COOKIE: 'D, dd M yy',
	ISO_8601: 'yy-mm-dd',
	RFC_822: 'D, d M y',
	RFC_850: 'DD, dd-M-y',
	RFC_1036: 'D, d M y',
	RFC_1123: 'D, d M yy',
	RFC_2822: 'D, d M yy',
	RSS: 'D, d M y', // RFC 822
	TICKS: '!',
	TIMESTAMP: '@',
	W3C: 'yy-mm-dd', // ISO 8601

	_ticksTo1970: (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 / 100) +
		Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000),

	/* Format a date object into a string value.
	   The format can be combinations of the following:
	   d  - day of month (no leading zero)
	   dd - day of month (two digit)
	   o  - day of year (no leading zeros)
	   oo - day of year (three digit)
	   D  - day name short
	   DD - day name long
	   m  - month of year (no leading zero)
	   mm - month of year (two digit)
	   M  - month name short
	   MM - month name long
	   y  - year (two digit)
	   yy - year (four digit)
	   @ - Unix timestamp (ms since 01/01/1970)
	   ! - Windows ticks (100ns since 01/01/0001)
	   '...' - literal text
	   '' - single quote

	   @param  format    string - the desired format of the date
	   @param  date      Date - the date value to format
	   @param  settings  Object - attributes include:
	                     dayNamesShort    string[7] - abbreviated names of the days from Sunday (optional)
	                     dayNames         string[7] - names of the days from Sunday (optional)
	                     monthNamesShort  string[12] - abbreviated names of the months (optional)
	                     monthNames       string[12] - names of the months (optional)
	   @return  string - the date in the above format */
	formatDate: function (format, date, settings) {
		if (!date)
			return '';
		var dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort;
		var dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames;
		var monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort;
		var monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames;
		// Check whether a format character is doubled
		var lookAhead = function(match) {
			var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match);
			if (matches)
				iFormat++;
			return matches;
		};
		// Format a number, with leading zero if necessary
		var formatNumber = function(match, value, len) {
			var num = '' + value;
			if (lookAhead(match))
				while (num.length < len)
					num = '0' + num;
			return num;
		};
		// Format a name, short or long as requested
		var formatName = function(match, value, shortNames, longNames) {
			return (lookAhead(match) ? longNames[value] : shortNames[value]);
		};
		var output = '';
		var literal = false;
		if (date)
			for (var iFormat = 0; iFormat < format.length; iFormat++) {
				if (literal)
					if (format.charAt(iFormat) == "'" && !lookAhead("'"))
						literal = false;
					else
						output += format.charAt(iFormat);
				else
					switch (format.charAt(iFormat)) {
						case 'd':
							output += formatNumber('d', date.getDate(), 2);
							break;
						case 'D':
							output += formatName('D', date.getDay(), dayNamesShort, dayNames);
							break;
						case 'o':
							output += formatNumber('o',
								Math.round((new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000), 3);
							break;
						case 'm':
							output += formatNumber('m', date.getMonth() + 1, 2);
							break;
						case 'M':
							output += formatName('M', date.getMonth(), monthNamesShort, monthNames);
							break;
						case 'y':
							output += (lookAhead('y') ? date.getFullYear() :
								(date.getYear() % 100 < 10 ? '0' : '') + date.getYear() % 100);
							break;
						case '@':
							output += date.getTime();
							break;
						case '!':
							output += date.getTime() * 10000 + this._ticksTo1970;
							break;
						case "'":
							if (lookAhead("'"))
								output += "'";
							else
								literal = true;
							break;
						default:
							output += format.charAt(iFormat);
					}
			}
		return output;
	},

	/* Extract all possible characters from the date format. */
	_possibleChars: function (format) {
		var chars = '';
		var literal = false;
		// Check whether a format character is doubled
		var lookAhead = function(match) {
			var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match);
			if (matches)
				iFormat++;
			return matches;
		};
		for (var iFormat = 0; iFormat < format.length; iFormat++)
			if (literal)
				if (format.charAt(iFormat) == "'" && !lookAhead("'"))
					literal = false;
				else
					chars += format.charAt(iFormat);
			else
				switch (format.charAt(iFormat)) {
					case 'd': case 'm': case 'y': case '@':
						chars += '0123456789';
						break;
					case 'D': case 'M':
						return null; // Accept anything
					case "'":
						if (lookAhead("'"))
							chars += "'";
						else
							literal = true;
						break;
					default:
						chars += format.charAt(iFormat);
				}
		return chars;
	},

	/* Get a setting value, defaulting if necessary. */
	_get: function(inst, name) {
		return inst.settings[name] !== undefined ?
			inst.settings[name] : this._defaults[name];
	},

	/* Parse existing date and initialise date picker. */
	_setDateFromField: function(inst, noDefault) {
		if (inst.input.val() == inst.lastVal) {
			return;
		}
		var dateFormat = this._get(inst, 'dateFormat');
		var dates = inst.lastVal = inst.input ? inst.input.val() : null;
		var date, defaultDate;
		date = defaultDate = this._getDefaultDate(inst);
		var settings = this._getFormatConfig(inst);
		try {
			date = this.parseDate(dateFormat, dates, settings) || defaultDate;
		} catch (event) {
			this.log(event);
			dates = (noDefault ? '' : dates);
		}
		inst.selectedDay = date.getDate();
		inst.drawMonth = inst.selectedMonth = date.getMonth();
		inst.drawYear = inst.selectedYear = date.getFullYear();
		inst.currentDay = (dates ? date.getDate() : 0);
		inst.currentMonth = (dates ? date.getMonth() : 0);
		inst.currentYear = (dates ? date.getFullYear() : 0);
		this._adjustInstDate(inst);
	},

	/* Retrieve the default date shown on opening. */
	_getDefaultDate: function(inst) {
		return this._restrictMinMax(inst,
			this._determineDate(inst, this._get(inst, 'defaultDate'), new Date()));
	},

	/* A date may be specified as an exact value or a relative one. */
	_determineDate: function(inst, date, defaultDate) {
		var offsetNumeric = function(offset) {
			var date = new Date();
			date.setDate(date.getDate() + offset);
			return date;
		};
		var offsetString = function(offset) {
			try {
				return $.datepicker.parseDate($.datepicker._get(inst, 'dateFormat'),
					offset, $.datepicker._getFormatConfig(inst));
			}
			catch (e) {
				// Ignore
			}
			var date = (offset.toLowerCase().match(/^c/) ?
				$.datepicker._getDate(inst) : null) || new Date();
			var year = date.getFullYear();
			var month = date.getMonth();
			var day = date.getDate();
			var pattern = /([+-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g;
			var matches = pattern.exec(offset);
			while (matches) {
				switch (matches[2] || 'd') {
					case 'd' : case 'D' :
						day += parseInt(matches[1],10); break;
					case 'w' : case 'W' :
						day += parseInt(matches[1],10) * 7; break;
					case 'm' : case 'M' :
						month += parseInt(matches[1],10);
						day = Math.min(day, $.datepicker._getDaysInMonth(year, month));
						break;
					case 'y': case 'Y' :
						year += parseInt(matches[1],10);
						day = Math.min(day, $.datepicker._getDaysInMonth(year, month));
						break;
				}
				matches = pattern.exec(offset);
			}
			return new Date(year, month, day);
		};
		var newDate = (date == null || date === '' ? defaultDate : (typeof date == 'string' ? offsetString(date) :
			(typeof date == 'number' ? (isNaN(date) ? defaultDate : offsetNumeric(date)) : new Date(date.getTime()))));
		newDate = (newDate && newDate.toString() == 'Invalid Date' ? defaultDate : newDate);
		if (newDate) {
			newDate.setHours(0);
			newDate.setMinutes(0);
			newDate.setSeconds(0);
			newDate.setMilliseconds(0);
		}
		return this._daylightSavingAdjust(newDate);
	},

	/* Handle switch to/from daylight saving.
	   Hours may be non-zero on daylight saving cut-over:
	   > 12 when midnight changeover, but then cannot generate
	   midnight datetime, so jump to 1AM, otherwise reset.
	   @param  date  (Date) the date to check
	   @return  (Date) the corrected date */
	_daylightSavingAdjust: function(date) {
		if (!date) return null;
		date.setHours(date.getHours() > 12 ? date.getHours() + 2 : 0);
		return date;
	},

	/* Set the date(s) directly. */
	_setDate: function(inst, date, noChange) {
		var clear = !date;
		var origMonth = inst.selectedMonth;
		var origYear = inst.selectedYear;
		var newDate = this._restrictMinMax(inst, this._determineDate(inst, date, new Date()));
		inst.selectedDay = inst.currentDay = newDate.getDate();
		inst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth();
		inst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear();
		if ((origMonth != inst.selectedMonth || origYear != inst.selectedYear) && !noChange)
			this._notifyChange(inst);
		this._adjustInstDate(inst);
		if (inst.input) {
			inst.input.val(clear ? '' : this._formatDate(inst));
		}
	},

	/* Retrieve the date(s) directly. */
	_getDate: function(inst) {
		var startDate = (!inst.currentYear || (inst.input && inst.input.val() == '') ? null :
			this._daylightSavingAdjust(new Date(
			inst.currentYear, inst.currentMonth, inst.currentDay)));
			return startDate;
	},

	/* Attach the onxxx handlers.  These are declared statically so
	 * they work with static code transformers like Caja.
	 */
	_attachHandlers: function(inst) {
		var stepMonths = this._get(inst, 'stepMonths');
		var id = '#' + inst.id;
		inst.dpDiv.find('[data-handler]').map(function () {
			var handler = {
				prev: function () {
					window['DP_jQuery_' + dpuuid].datepicker._adjustDate(id, -stepMonths, 'M');
				},
				next: function () {
					window['DP_jQuery_' + dpuuid].datepicker._adjustDate(id, +stepMonths, 'M');
				},
				hide: function () {
					window['DP_jQuery_' + dpuuid].datepicker._hideDatepicker();
				},
				today: function () {
					window['DP_jQuery_' + dpuuid].datepicker._gotoToday(id);
				},
				selectDay: function () {
					window['DP_jQuery_' + dpuuid].datepicker._selectDay(id, +this.getAttribute('data-month'), +this.getAttribute('data-year'), this);
					return false;
				},
				selectMonth: function () {
					window['DP_jQuery_' + dpuuid].datepicker._selectMonthYear(id, this, 'M');
					return false;
				},
				selectYear: function () {
					window['DP_jQuery_' + dpuuid].datepicker._selectMonthYear(id, this, 'Y');
					return false;
				}
			};
			$(this).bind(this.getAttribute('data-event'), handler[this.getAttribute('data-handler')]);
		});
	},
	
	/* Generate the HTML for the current state of the date picker. */
	_generateHTML: function(inst) {
		var today = new Date();
		today = this._daylightSavingAdjust(
			new Date(today.getFullYear(), today.getMonth(), today.getDate())); // clear time
		var isRTL = this._get(inst, 'isRTL');
		var showButtonPanel = this._get(inst, 'showButtonPanel');
		var hideIfNoPrevNext = this._get(inst, 'hideIfNoPrevNext');
		var navigationAsDateFormat = this._get(inst, 'navigationAsDateFormat');
		var numMonths = this._getNumberOfMonths(inst);
		var showCurrentAtPos = this._get(inst, 'showCurrentAtPos');
		var stepMonths = this._get(inst, 'stepMonths');
		var isMultiMonth = (numMonths[0] != 1 || numMonths[1] != 1);
		var currentDate = this._daylightSavingAdjust((!inst.currentDay ? new Date(9999, 9, 9) :
			new Date(inst.currentYear, inst.currentMonth, inst.currentDay)));
		var minDate = this._getMinMaxDate(inst, 'min');
		var maxDate = this._getMinMaxDate(inst, 'max');
		var drawMonth = inst.drawMonth - showCurrentAtPos;
		var drawYear = inst.drawYear;
		if (drawMonth < 0) {
			drawMonth += 12;
			drawYear--;
		}
		if (maxDate) {
			var maxDraw = this._daylightSavingAdjust(new Date(maxDate.getFullYear(),
				maxDate.getMonth() - (numMonths[0] * numMonths[1]) + 1, maxDate.getDate()));
			maxDraw = (minDate && maxDraw < minDate ? minDate : maxDraw);
			while (this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1)) > maxDraw) {
				drawMonth--;
				if (drawMonth < 0) {
					drawMonth = 11;
					drawYear--;
				}
			}
		}
		inst.drawMonth = drawMonth;
		inst.drawYear = drawYear;
		var prevText = this._get(inst, 'prevText');
		prevText = (!navigationAsDateFormat ? prevText : this.formatDate(prevText,
			this._daylightSavingAdjust(new Date(drawYear, drawMonth - stepMonths, 1)),
			this._getFormatConfig(inst)));
		var prev = (this._canAdjustMonth(inst, -1, drawYear, drawMonth) ?
			'<a class="ui-datepicker-prev ui-corner-all" data-handler="prev" data-event="click"' +
			' title="' + prevText + '"><span class="ui-icon ui-icon-circle-triangle-' + ( isRTL ? 'e' : 'w') + '">' + prevText + '</span></a>' :
			(hideIfNoPrevNext ? '' : '<a class="ui-datepicker-prev ui-corner-all ui-state-disabled" title="'+ prevText +'"><span class="ui-icon ui-icon-circle-triangle-' + ( isRTL ? 'e' : 'w') + '">' + prevText + '</span></a>'));
		var nextText = this._get(inst, 'nextText');
		nextText = (!navigationAsDateFormat ? nextText : this.formatDate(nextText,
			this._daylightSavingAdjust(new Date(drawYear, drawMonth + stepMonths, 1)),
			this._getFormatConfig(inst)));
		var next = (this._canAdjustMonth(inst, +1, drawYear, drawMonth) ?
			'<a class="ui-datepicker-next ui-corner-all" data-handler="next" data-event="click"' +
			' title="' + nextText + '"><span class="ui-icon ui-icon-circle-triangle-' + ( isRTL ? 'w' : 'e') + '">' + nextText + '</span></a>' :
			(hideIfNoPrevNext ? '' : '<a class="ui-datepicker-next ui-corner-all ui-state-disabled" title="'+ nextText + '"><span class="ui-icon ui-icon-circle-triangle-' + ( isRTL ? 'w' : 'e') + '">' + nextText + '</span></a>'));
		var currentText = this._get(inst, 'currentText');
		var gotoDate = (this._get(inst, 'gotoCurrent') && inst.currentDay ? currentDate : today);
		currentText = (!navigationAsDateFormat ? currentText :
			this.formatDate(currentText, gotoDate, this._getFormatConfig(inst)));
		var controls = (!inst.inline ? '<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all" data-handler="hide" data-event="click">' +
			this._get(inst, 'closeText') + '</button>' : '');
		var buttonPanel = (showButtonPanel) ? '<div class="ui-datepicker-buttonpane ui-widget-content">' + (isRTL ? controls : '') +
			(this._isInRange(inst, gotoDate) ? '<button type="button" class="ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all" data-handler="today" data-event="click"' +
			'>' + currentText + '</button>' : '') + (isRTL ? '' : controls) + '</div>' : '';
		var firstDay = parseInt(this._get(inst, 'firstDay'),10);
		firstDay = (isNaN(firstDay) ? 0 : firstDay);
		var showWeek = this._get(inst, 'showWeek');
		var dayNames = this._get(inst, 'dayNames');
		var dayNamesShort = this._get(inst, 'dayNamesShort');
		var dayNamesMin = this._get(inst, 'dayNamesMin');
		var monthNames = this._get(inst, 'monthNames');
		var monthNamesShort = this._get(inst, 'monthNamesShort');
		var beforeShowDay = this._get(inst, 'beforeShowDay');
		var showOtherMonths = this._get(inst, 'showOtherMonths');
		var selectOtherMonths = this._get(inst, 'selectOtherMonths');
		var calculateWeek = this._get(inst, 'calculateWeek') || this.iso8601Week;
		var defaultDate = this._getDefaultDate(inst);
		var html = '';
		for (var row = 0; row < numMonths[0]; row++) {
			var group = '';
			this.maxRows = 4;
			for (var col = 0; col < numMonths[1]; col++) {
				var selectedDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, inst.selectedDay));
				var cornerClass = ' ui-corner-all';
				var calender = '';
				if (isMultiMonth) {
					calender += '<div class="ui-datepicker-group';
					if (numMonths[1] > 1)
						switch (col) {
							case 0: calender += ' ui-datepicker-group-first';
								cornerClass = ' ui-corner-' + (isRTL ? 'right' : 'left'); break;
							case numMonths[1]-1: calender += ' ui-datepicker-group-last';
								cornerClass = ' ui-corner-' + (isRTL ? 'left' : 'right'); break;
							default: calender += ' ui-datepicker-group-middle'; cornerClass = ''; break;
						}
					calender += '">';
				}
				calender += '<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix' + cornerClass + '">' +
					(/all|left/.test(cornerClass) && row == 0 ? (isRTL ? next : prev) : '') +
					(/all|right/.test(cornerClass) && row == 0 ? (isRTL ? prev : next) : '') +
					this._generateMonthYearHeader(inst, drawMonth, drawYear, minDate, maxDate,
					row > 0 || col > 0, monthNames, monthNamesShort) + // draw month headers
					'</div><table class="ui-datepicker-calendar"><thead>' +
					'<tr>';
				var thead = (showWeek ? '<th class="ui-datepicker-week-col">' + this._get(inst, 'weekHeader') + '</th>' : '');
				for (var dow = 0; dow < 7; dow++) { // days of the week
					var day = (dow + firstDay) % 7;
					thead += '<th' + ((dow + firstDay + 6) % 7 >= 5 ? ' class="ui-datepicker-week-end"' : '') + '>' +
						'<span title="' + dayNames[day] + '">' + dayNamesMin[day] + '</span></th>';
				}
				calender += thead + '</tr></thead><tbody>';
				var daysInMonth = this._getDaysInMonth(drawYear, drawMonth);
				if (drawYear == inst.selectedYear && drawMonth == inst.selectedMonth)
					inst.selectedDay = Math.min(inst.selectedDay, daysInMonth);
				var leadDays = (this._getFirstDayOfMonth(drawYear, drawMonth) - firstDay + 7) % 7;
				var curRows = Math.ceil((leadDays + daysInMonth) / 7); // calculate the number of rows to generate
				var numRows = (isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows); //If multiple months, use the higher number of rows (see #7043)
				this.maxRows = numRows;
				var printDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1 - leadDays));
				for (var dRow = 0; dRow < numRows; dRow++) { // create date picker rows
					calender += '<tr>';
					var tbody = (!showWeek ? '' : '<td class="ui-datepicker-week-col">' +
						this._get(inst, 'calculateWeek')(printDate) + '</td>');
					for (var dow = 0; dow < 7; dow++) { // create date picker days
						var daySettings = (beforeShowDay ?
							beforeShowDay.apply((inst.input ? inst.input[0] : null), [printDate]) : [true, '']);
						var otherMonth = (printDate.getMonth() != drawMonth);
						var unselectable = (otherMonth && !selectOtherMonths) || !daySettings[0] ||
							(minDate && printDate < minDate) || (maxDate && printDate > maxDate);
						tbody += '<td class="' +
							((dow + firstDay + 6) % 7 >= 5 ? ' ui-datepicker-week-end' : '') + // highlight weekends
							(otherMonth ? ' ui-datepicker-other-month' : '') + // highlight days from other months
							((printDate.getTime() == selectedDate.getTime() && drawMonth == inst.selectedMonth && inst._keyEvent) || // user pressed key
							(defaultDate.getTime() == printDate.getTime() && defaultDate.getTime() == selectedDate.getTime()) ?
							// or defaultDate is current printedDate and defaultDate is selectedDate
							' ' + this._dayOverClass : '') + // highlight selected day
							(unselectable ? ' ' + this._unselectableClass + ' ui-state-disabled': '') +  // highlight unselectable days
							(otherMonth && !showOtherMonths ? '' : ' ' + daySettings[1] + // highlight custom dates
							(printDate.getTime() == currentDate.getTime() ? ' ' + this._currentClass : '') + // highlight selected day
							(printDate.getTime() == today.getTime() ? ' ui-datepicker-today' : '')) + '"' + // highlight today (if different)
							((!otherMonth || showOtherMonths) && daySettings[2] ? ' title="' + daySettings[2] + '"' : '') + // cell title
							(unselectable ? '' : ' data-handler="selectDay" data-event="click" data-month="' + printDate.getMonth() + '" data-year="' + printDate.getFullYear() + '"') + '>' + // actions
							(otherMonth && !showOtherMonths ? '&#xa0;' : // display for other months
							(unselectable ? '<span class="ui-state-default">' + printDate.getDate() + '</span>' : '<a class="ui-state-default' +
							(printDate.getTime() == today.getTime() ? ' ui-state-highlight' : '') +
							(printDate.getTime() == currentDate.getTime() ? ' ui-state-active' : '') + // highlight selected day
							(otherMonth ? ' ui-priority-secondary' : '') + // distinguish dates from other months
							'" href="#">' + printDate.getDate() + '</a>')) + '</td>'; // display selectable date
						printDate.setDate(printDate.getDate() + 1);
						printDate = this._daylightSavingAdjust(printDate);
					}
					calender += tbody + '</tr>';
				}
				drawMonth++;
				if (drawMonth > 11) {
					drawMonth = 0;
					drawYear++;
				}
				calender += '</tbody></table>' + (isMultiMonth ? '</div>' + 
							((numMonths[0] > 0 && col == numMonths[1]-1) ? '<div class="ui-datepicker-row-break"></div>' : '') : '');
				group += calender;
			}
			html += group;
		}
		html += buttonPanel + ($.browser.msie && parseInt($.browser.version,10) < 7 && !inst.inline ?
			'<iframe src="javascript:false;" class="ui-datepicker-cover" frameborder="0"></iframe>' : '');
		inst._keyEvent = false;
		return html;
	},

	/* Generate the month and year header. */
	_generateMonthYearHeader: function(inst, drawMonth, drawYear, minDate, maxDate,
			secondary, monthNames, monthNamesShort) {
		var changeMonth = this._get(inst, 'changeMonth');
		var changeYear = this._get(inst, 'changeYear');
		var showMonthAfterYear = this._get(inst, 'showMonthAfterYear');
		var html = '<div class="ui-datepicker-title">';
		var monthHtml = '';
		// month selection
		if (secondary || !changeMonth)
			monthHtml += '<span class="ui-datepicker-month">' + monthNames[drawMonth] + '</span>';
		else {
			var inMinYear = (minDate && minDate.getFullYear() == drawYear);
			var inMaxYear = (maxDate && maxDate.getFullYear() == drawYear);
			monthHtml += '<select class="ui-datepicker-month" data-handler="selectMonth" data-event="change">';
			for (var month = 0; month < 12; month++) {
				if ((!inMinYear || month >= minDate.getMonth()) &&
						(!inMaxYear || month <= maxDate.getMonth()))
					monthHtml += '<option value="' + month + '"' +
						(month == drawMonth ? ' selected="selected"' : '') +
						'>' + monthNamesShort[month] + '</option>';
			}
			monthHtml += '</select>';
		}
		if (!showMonthAfterYear)
			html += monthHtml + (secondary || !(changeMonth && changeYear) ? '&#xa0;' : '');
		// year selection
		if ( !inst.yearshtml ) {
			inst.yearshtml = '';
			if (secondary || !changeYear)
				html += '<span class="ui-datepicker-year">' + drawYear + '</span>';
			else {
				// determine range of years to display
				var years = this._get(inst, 'yearRange').split(':');
				var thisYear = new Date().getFullYear();
				var determineYear = function(value) {
					var year = (value.match(/c[+-].*/) ? drawYear + parseInt(value.substring(1), 10) :
						(value.match(/[+-].*/) ? thisYear + parseInt(value, 10) :
						parseInt(value, 10)));
					return (isNaN(year) ? thisYear : year);
				};
				var year = determineYear(years[0]);
				var endYear = Math.max(year, determineYear(years[1] || ''));
				year = (minDate ? Math.max(year, minDate.getFullYear()) : year);
				endYear = (maxDate ? Math.min(endYear, maxDate.getFullYear()) : endYear);
				inst.yearshtml += '<select class="ui-datepicker-year" data-handler="selectYear" data-event="change">';
				for (; year <= endYear; year++) {
					inst.yearshtml += '<option value="' + year + '"' +
						(year == drawYear ? ' selected="selected"' : '') +
						'>' + year + '</option>';
				}
				inst.yearshtml += '</select>';
				
				html += inst.yearshtml;
				inst.yearshtml = null;
			}
		}
		html += this._get(inst, 'yearSuffix');
		if (showMonthAfterYear)
			html += (secondary || !(changeMonth && changeYear) ? '&#xa0;' : '') + monthHtml;
		html += '</div>'; // Close datepicker_header
		return html;
	},

	/* Adjust one of the date sub-fields. */
	_adjustInstDate: function(inst, offset, period) {
		var year = inst.drawYear + (period == 'Y' ? offset : 0);
		var month = inst.drawMonth + (period == 'M' ? offset : 0);
		var day = Math.min(inst.selectedDay, this._getDaysInMonth(year, month)) +
			(period == 'D' ? offset : 0);
		var date = this._restrictMinMax(inst,
			this._daylightSavingAdjust(new Date(year, month, day)));
		inst.selectedDay = date.getDate();
		inst.drawMonth = inst.selectedMonth = date.getMonth();
		inst.drawYear = inst.selectedYear = date.getFullYear();
		if (period == 'M' || period == 'Y')
			this._notifyChange(inst);
	},

	/* Ensure a date is within any min/max bounds. */
	_restrictMinMax: function(inst, date) {
		var minDate = this._getMinMaxDate(inst, 'min');
		var maxDate = this._getMinMaxDate(inst, 'max');
		var newDate = (minDate && date < minDate ? minDate : date);
		newDate = (maxDate && newDate > maxDate ? maxDate : newDate);
		return newDate;
	},

	/* Notify change of month/year. */
	_notifyChange: function(inst) {
		var onChange = this._get(inst, 'onChangeMonthYear');
		if (onChange)
			onChange.apply((inst.input ? inst.input[0] : null),
				[inst.selectedYear, inst.selectedMonth + 1, inst]);
	},

	/* Determine the number of months to show. */
	_getNumberOfMonths: function(inst) {
		var numMonths = this._get(inst, 'numberOfMonths');
		return (numMonths == null ? [1, 1] : (typeof numMonths == 'number' ? [1, numMonths] : numMonths));
	},

	/* Determine the current maximum date - ensure no time components are set. */
	_getMinMaxDate: function(inst, minMax) {
		return this._determineDate(inst, this._get(inst, minMax + 'Date'), null);
	},

	/* Find the number of days in a given month. */
	_getDaysInMonth: function(year, month) {
		return 32 - this._daylightSavingAdjust(new Date(year, month, 32)).getDate();
	},

	/* Find the day of the week of the first of a month. */
	_getFirstDayOfMonth: function(year, month) {
		return new Date(year, month, 1).getDay();
	},

	/* Determines if we should allow a "next/prev" month display change. */
	_canAdjustMonth: function(inst, offset, curYear, curMonth) {
		var numMonths = this._getNumberOfMonths(inst);
		var date = this._daylightSavingAdjust(new Date(curYear,
			curMonth + (offset < 0 ? offset : numMonths[0] * numMonths[1]), 1));
		if (offset < 0)
			date.setDate(this._getDaysInMonth(date.getFullYear(), date.getMonth()));
		return this._isInRange(inst, date);
	},

	/* Is the given date in the accepted range? */
	_isInRange: function(inst, date) {
		var minDate = this._getMinMaxDate(inst, 'min');
		var maxDate = this._getMinMaxDate(inst, 'max');
		return ((!minDate || date.getTime() >= minDate.getTime()) &&
			(!maxDate || date.getTime() <= maxDate.getTime()));
	},

	/* Provide the configuration settings for formatting/parsing. */
	_getFormatConfig: function(inst) {
		var shortYearCutoff = this._get(inst, 'shortYearCutoff');
		shortYearCutoff = (typeof shortYearCutoff != 'string' ? shortYearCutoff :
			new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10));
		return {shortYearCutoff: shortYearCutoff,
			dayNamesShort: this._get(inst, 'dayNamesShort'), dayNames: this._get(inst, 'dayNames'),
			monthNamesShort: this._get(inst, 'monthNamesShort'), monthNames: this._get(inst, 'monthNames')};
	},

	/* Format the given date for display. */
	_formatDate: function(inst, day, month, year) {
		if (!day) {
			inst.currentDay = inst.selectedDay;
			inst.currentMonth = inst.selectedMonth;
			inst.currentYear = inst.selectedYear;
		}
		var date = (day ? (typeof day == 'object' ? day :
			this._daylightSavingAdjust(new Date(year, month, day))) :
			this._daylightSavingAdjust(new Date(inst.currentYear, inst.currentMonth, inst.currentDay)));
		return this.formatDate(this._get(inst, 'dateFormat'), date, this._getFormatConfig(inst));
	}
});

/*
 * Bind hover events for datepicker elements.
 * Done via delegate so the binding only occurs once in the lifetime of the parent div.
 * Global instActive, set by _updateDatepicker allows the handlers to find their way back to the active picker.
 */ 
function bindHover(dpDiv) {
	var selector = 'button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a';
	return dpDiv.bind('mouseout', function(event) {
			var elem = $( event.target ).closest( selector );
			if ( !elem.length ) {
				return;
			}
			elem.removeClass( "ui-state-hover ui-datepicker-prev-hover ui-datepicker-next-hover" );
		})
		.bind('mouseover', function(event) {
			var elem = $( event.target ).closest( selector );
			if ($.datepicker._isDisabledDatepicker( instActive.inline ? dpDiv.parent()[0] : instActive.input[0]) ||
					!elem.length ) {
				return;
			}
			elem.parents('.ui-datepicker-calendar').find('a').removeClass('ui-state-hover');
			elem.addClass('ui-state-hover');
			if (elem.hasClass('ui-datepicker-prev')) elem.addClass('ui-datepicker-prev-hover');
			if (elem.hasClass('ui-datepicker-next')) elem.addClass('ui-datepicker-next-hover');
		});
}

/* jQuery extend now ignores nulls! */
function extendRemove(target, props) {
	$.extend(target, props);
	for (var name in props)
		if (props[name] == null || props[name] == undefined)
			target[name] = props[name];
	return target;
};

/* Determine whether an object is an array. */
function isArray(a) {
	return (a && (($.browser.safari && typeof a == 'object' && a.length) ||
		(a.constructor && a.constructor.toString().match(/\Array\(\)/))));
};

/* Invoke the datepicker functionality.
   @param  options  string - a command, optionally followed by additional parameters or
                    Object - settings for attaching new datepicker functionality
   @return  jQuery object */
$.fn.datepicker = function(options){
	
	/* Verify an empty collection wasn't passed - Fixes #6976 */
	if ( !this.length ) {
		return this;
	}
	
	/* Initialise the date picker. */
	if (!$.datepicker.initialized) {
		$(document).mousedown($.datepicker._checkExternalClick).
			find('body').append($.datepicker.dpDiv);
		$.datepicker.initialized = true;
	}

	var otherArgs = Array.prototype.slice.call(arguments, 1);
	if (typeof options == 'string' && (options == 'isDisabled' || options == 'getDate' || options == 'widget'))
		return $.datepicker['_' + options + 'Datepicker'].
			apply($.datepicker, [this[0]].concat(otherArgs));
	if (options == 'option' && arguments.length == 2 && typeof arguments[1] == 'string')
		return $.datepicker['_' + options + 'Datepicker'].
			apply($.datepicker, [this[0]].concat(otherArgs));
	return this.each(function() {
		typeof options == 'string' ?
			$.datepicker['_' + options + 'Datepicker'].
				apply($.datepicker, [this].concat(otherArgs)) :
			$.datepicker._attachDatepicker(this, options);
	});
};

$.datepicker = new Datepicker(); // singleton instance
$.datepicker.initialized = false;
$.datepicker.uuid = new Date().getTime();
$.datepicker.version = "1.8.22";

// Workaround for #4055
// Add another global to avoid noConflict issues with inline event handlers
window['DP_jQuery_' + dpuuid] = $;

})(jQuery);

(function( $, undefined ) {

var uiDialogClasses =
		'ui-dialog ' +
		'ui-widget ' +
		'ui-widget-content ' +
		'ui-corner-all ',
	sizeRelatedOptions = {
		buttons: true,
		height: true,
		maxHeight: true,
		maxWidth: true,
		minHeight: true,
		minWidth: true,
		width: true
	},
	resizableRelatedOptions = {
		maxHeight: true,
		maxWidth: true,
		minHeight: true,
		minWidth: true
	},
	// support for jQuery 1.3.2 - handle common attrFn methods for dialog
	attrFn = $.attrFn || {
		val: true,
		css: true,
		html: true,
		text: true,
		data: true,
		width: true,
		height: true,
		offset: true,
		click: true
	};

$.widget("ui.dialog", {
	options: {
		autoOpen: true,
		buttons: {},
		closeOnEscape: true,
		closeText: 'close',
		dialogClass: '',
		draggable: true,
		hide: null,
		height: 'auto',
		maxHeight: false,
		maxWidth: false,
		minHeight: 150,
		minWidth: 150,
		modal: false,
		position: {
			my: 'center',
			at: 'center',
			collision: 'fit',
			// ensure that the titlebar is never outside the document
			using: function(pos) {
				var topOffset = $(this).css(pos).offset().top;
				if (topOffset < 0) {
					$(this).css('top', pos.top - topOffset);
				}
			}
		},
		resizable: true,
		show: null,
		stack: true,
		title: '',
		width: 300,
		zIndex: 1000
	},

	_create: function() {
		this.originalTitle = this.element.attr('title');
		// #5742 - .attr() might return a DOMElement
		if ( typeof this.originalTitle !== "string" ) {
			this.originalTitle = "";
		}

		this.options.title = this.options.title || this.originalTitle;
		var self = this,
			options = self.options,

			title = options.title || '&#160;',
			titleId = $.ui.dialog.getTitleId(self.element),

			uiDialog = (self.uiDialog = $('<div></div>'))
				.appendTo(document.body)
				.hide()
				.addClass(uiDialogClasses + options.dialogClass)
				.css({
					zIndex: options.zIndex
				})
				// setting tabIndex makes the div focusable
				// setting outline to 0 prevents a border on focus in Mozilla
				.attr('tabIndex', -1).css('outline', 0).keydown(function(event) {
					if (options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode &&
						event.keyCode === $.ui.keyCode.ESCAPE) {
						
						self.close(event);
						event.preventDefault();
					}
				})
				.attr({
					role: 'dialog',
					'aria-labelledby': titleId
				})
				.mousedown(function(event) {
					self.moveToTop(false, event);
				}),

			uiDialogContent = self.element
				.show()
				.removeAttr('title')
				.addClass(
					'ui-dialog-content ' +
					'ui-widget-content')
				.appendTo(uiDialog),

			uiDialogTitlebar = (self.uiDialogTitlebar = $('<div></div>'))
				.addClass(
					'ui-dialog-titlebar ' +
					'ui-widget-header ' +
					'ui-corner-all ' +
					'ui-helper-clearfix'
				)
				.prependTo(uiDialog),

			uiDialogTitlebarClose = $('<a href="#"></a>')
				.addClass(
					'ui-dialog-titlebar-close ' +
					'ui-corner-all'
				)
				.attr('role', 'button')
				.hover(
					function() {
						uiDialogTitlebarClose.addClass('ui-state-hover');
					},
					function() {
						uiDialogTitlebarClose.removeClass('ui-state-hover');
					}
				)
				.focus(function() {
					uiDialogTitlebarClose.addClass('ui-state-focus');
				})
				.blur(function() {
					uiDialogTitlebarClose.removeClass('ui-state-focus');
				})
				.click(function(event) {
					self.close(event);
					return false;
				})
				.appendTo(uiDialogTitlebar),

			uiDialogTitlebarCloseText = (self.uiDialogTitlebarCloseText = $('<span></span>'))
				.addClass(
					'ui-icon ' +
					'ui-icon-closethick'
				)
				.text(options.closeText)
				.appendTo(uiDialogTitlebarClose),

			uiDialogTitle = $('<span></span>')
				.addClass('ui-dialog-title')
				.attr('id', titleId)
				.html(title)
				.prependTo(uiDialogTitlebar);

		//handling of deprecated beforeclose (vs beforeClose) option
		//Ticket #4669 http://dev.jqueryui.com/ticket/4669
		//TODO: remove in 1.9pre
		if ($.isFunction(options.beforeclose) && !$.isFunction(options.beforeClose)) {
			options.beforeClose = options.beforeclose;
		}

		uiDialogTitlebar.find("*").add(uiDialogTitlebar).disableSelection();

		if (options.draggable && $.fn.draggable) {
			self._makeDraggable();
		}
		if (options.resizable && $.fn.resizable) {
			self._makeResizable();
		}

		self._createButtons(options.buttons);
		self._isOpen = false;

		if ($.fn.bgiframe) {
			uiDialog.bgiframe();
		}
	},

	_init: function() {
		if ( this.options.autoOpen ) {
			this.open();
		}
	},

	destroy: function() {
		var self = this;
		
		if (self.overlay) {
			self.overlay.destroy();
		}
		self.uiDialog.hide();
		self.element
			.unbind('.dialog')
			.removeData('dialog')
			.removeClass('ui-dialog-content ui-widget-content')
			.hide().appendTo('body');
		self.uiDialog.remove();

		if (self.originalTitle) {
			self.element.attr('title', self.originalTitle);
		}

		return self;
	},

	widget: function() {
		return this.uiDialog;
	},

	close: function(event) {
		var self = this,
			maxZ, thisZ;
		
		if (false === self._trigger('beforeClose', event)) {
			return;
		}

		if (self.overlay) {
			self.overlay.destroy();
		}
		self.uiDialog.unbind('keypress.ui-dialog');

		self._isOpen = false;

		if (self.options.hide) {
			self.uiDialog.hide(self.options.hide, function() {
				self._trigger('close', event);
			});
		} else {
			self.uiDialog.hide();
			self._trigger('close', event);
		}

		$.ui.dialog.overlay.resize();

		// adjust the maxZ to allow other modal dialogs to continue to work (see #4309)
		if (self.options.modal) {
			maxZ = 0;
			$('.ui-dialog').each(function() {
				if (this !== self.uiDialog[0]) {
					thisZ = $(this).css('z-index');
					if(!isNaN(thisZ)) {
						maxZ = Math.max(maxZ, thisZ);
					}
				}
			});
			$.ui.dialog.maxZ = maxZ;
		}

		return self;
	},

	isOpen: function() {
		return this._isOpen;
	},

	// the force parameter allows us to move modal dialogs to their correct
	// position on open
	moveToTop: function(force, event) {
		var self = this,
			options = self.options,
			saveScroll;

		if ((options.modal && !force) ||
			(!options.stack && !options.modal)) {
			return self._trigger('focus', event);
		}

		if (options.zIndex > $.ui.dialog.maxZ) {
			$.ui.dialog.maxZ = options.zIndex;
		}
		if (self.overlay) {
			$.ui.dialog.maxZ += 1;
			self.overlay.$el.css('z-index', $.ui.dialog.overlay.maxZ = $.ui.dialog.maxZ);
		}

		//Save and then restore scroll since Opera 9.5+ resets when parent z-Index is changed.
		//  http://ui.jquery.com/bugs/ticket/3193
		saveScroll = { scrollTop: self.element.scrollTop(), scrollLeft: self.element.scrollLeft() };
		$.ui.dialog.maxZ += 1;
		self.uiDialog.css('z-index', $.ui.dialog.maxZ);
		self.element.attr(saveScroll);
		self._trigger('focus', event);

		return self;
	},

	open: function() {
		if (this._isOpen) { return; }

		var self = this,
			options = self.options,
			uiDialog = self.uiDialog;

		self.overlay = options.modal ? new $.ui.dialog.overlay(self) : null;
		self._size();
		self._position(options.position);
		uiDialog.show(options.show);
		self.moveToTop(true);

		// prevent tabbing out of modal dialogs
		if ( options.modal ) {
			uiDialog.bind( "keydown.ui-dialog", function( event ) {
				if ( event.keyCode !== $.ui.keyCode.TAB ) {
					return;
				}

				var tabbables = $(':tabbable', this),
					first = tabbables.filter(':first'),
					last  = tabbables.filter(':last');

				if (event.target === last[0] && !event.shiftKey) {
					first.focus(1);
					return false;
				} else if (event.target === first[0] && event.shiftKey) {
					last.focus(1);
					return false;
				}
			});
		}

		// set focus to the first tabbable element in the content area or the first button
		// if there are no tabbable elements, set focus on the dialog itself
		$(self.element.find(':tabbable').get().concat(
			uiDialog.find('.ui-dialog-buttonpane :tabbable').get().concat(
				uiDialog.get()))).eq(0).focus();

		self._isOpen = true;
		self._trigger('open');

		return self;
	},

	_createButtons: function(buttons) {
		var self = this,
			hasButtons = false,
			uiDialogButtonPane = $('<div></div>')
				.addClass(
					'ui-dialog-buttonpane ' +
					'ui-widget-content ' +
					'ui-helper-clearfix'
				),
			uiButtonSet = $( "<div></div>" )
				.addClass( "ui-dialog-buttonset" )
				.appendTo( uiDialogButtonPane );

		// if we already have a button pane, remove it
		self.uiDialog.find('.ui-dialog-buttonpane').remove();

		if (typeof buttons === 'object' && buttons !== null) {
			$.each(buttons, function() {
				return !(hasButtons = true);
			});
		}
		if (hasButtons) {
			$.each(buttons, function(name, props) {
				props = $.isFunction( props ) ?
					{ click: props, text: name } :
					props;
				var button = $('<button type="button"></button>')
					.click(function() {
						props.click.apply(self.element[0], arguments);
					})
					.appendTo(uiButtonSet);
				// can't use .attr( props, true ) with jQuery 1.3.2.
				$.each( props, function( key, value ) {
					if ( key === "click" ) {
						return;
					}
					if ( key in attrFn ) {
						button[ key ]( value );
					} else {
						button.attr( key, value );
					}
				});
				if ($.fn.button) {
					button.button();
				}
			});
			uiDialogButtonPane.appendTo(self.uiDialog);
		}
	},

	_makeDraggable: function() {
		var self = this,
			options = self.options,
			doc = $(document),
			heightBeforeDrag;

		function filteredUi(ui) {
			return {
				position: ui.position,
				offset: ui.offset
			};
		}

		self.uiDialog.draggable({
			cancel: '.ui-dialog-content, .ui-dialog-titlebar-close',
			handle: '.ui-dialog-titlebar',
			containment: 'document',
			start: function(event, ui) {
				heightBeforeDrag = options.height === "auto" ? "auto" : $(this).height();
				$(this).height($(this).height()).addClass("ui-dialog-dragging");
				self._trigger('dragStart', event, filteredUi(ui));
			},
			drag: function(event, ui) {
				self._trigger('drag', event, filteredUi(ui));
			},
			stop: function(event, ui) {
				options.position = [ui.position.left - doc.scrollLeft(),
					ui.position.top - doc.scrollTop()];
				$(this).removeClass("ui-dialog-dragging").height(heightBeforeDrag);
				self._trigger('dragStop', event, filteredUi(ui));
				$.ui.dialog.overlay.resize();
			}
		});
	},

	_makeResizable: function(handles) {
		handles = (handles === undefined ? this.options.resizable : handles);
		var self = this,
			options = self.options,
			// .ui-resizable has position: relative defined in the stylesheet
			// but dialogs have to use absolute or fixed positioning
			position = self.uiDialog.css('position'),
			resizeHandles = (typeof handles === 'string' ?
				handles	:
				'n,e,s,w,se,sw,ne,nw'
			);

		function filteredUi(ui) {
			return {
				originalPosition: ui.originalPosition,
				originalSize: ui.originalSize,
				position: ui.position,
				size: ui.size
			};
		}

		self.uiDialog.resizable({
			cancel: '.ui-dialog-content',
			containment: 'document',
			alsoResize: self.element,
			maxWidth: options.maxWidth,
			maxHeight: options.maxHeight,
			minWidth: options.minWidth,
			minHeight: self._minHeight(),
			handles: resizeHandles,
			start: function(event, ui) {
				$(this).addClass("ui-dialog-resizing");
				self._trigger('resizeStart', event, filteredUi(ui));
			},
			resize: function(event, ui) {
				self._trigger('resize', event, filteredUi(ui));
			},
			stop: function(event, ui) {
				$(this).removeClass("ui-dialog-resizing");
				options.height = $(this).height();
				options.width = $(this).width();
				self._trigger('resizeStop', event, filteredUi(ui));
				$.ui.dialog.overlay.resize();
			}
		})
		.css('position', position)
		.find('.ui-resizable-se').addClass('ui-icon ui-icon-grip-diagonal-se');
	},

	_minHeight: function() {
		var options = this.options;

		if (options.height === 'auto') {
			return options.minHeight;
		} else {
			return Math.min(options.minHeight, options.height);
		}
	},

	_position: function(position) {
		var myAt = [],
			offset = [0, 0],
			isVisible;

		if (position) {
			// deep extending converts arrays to objects in jQuery <= 1.3.2 :-(
	//		if (typeof position == 'string' || $.isArray(position)) {
	//			myAt = $.isArray(position) ? position : position.split(' ');

			if (typeof position === 'string' || (typeof position === 'object' && '0' in position)) {
				myAt = position.split ? position.split(' ') : [position[0], position[1]];
				if (myAt.length === 1) {
					myAt[1] = myAt[0];
				}

				$.each(['left', 'top'], function(i, offsetPosition) {
					if (+myAt[i] === myAt[i]) {
						offset[i] = myAt[i];
						myAt[i] = offsetPosition;
					}
				});

				position = {
					my: myAt.join(" "),
					at: myAt.join(" "),
					offset: offset.join(" ")
				};
			} 

			position = $.extend({}, $.ui.dialog.prototype.options.position, position);
		} else {
			position = $.ui.dialog.prototype.options.position;
		}

		// need to show the dialog to get the actual offset in the position plugin
		isVisible = this.uiDialog.is(':visible');
		if (!isVisible) {
			this.uiDialog.show();
		}
		this.uiDialog
			// workaround for jQuery bug #5781 http://dev.jquery.com/ticket/5781
			.css({ top: 0, left: 0 })
			.position($.extend({ of: window }, position));
		if (!isVisible) {
			this.uiDialog.hide();
		}
	},

	_setOptions: function( options ) {
		var self = this,
			resizableOptions = {},
			resize = false;

		$.each( options, function( key, value ) {
			self._setOption( key, value );
			
			if ( key in sizeRelatedOptions ) {
				resize = true;
			}
			if ( key in resizableRelatedOptions ) {
				resizableOptions[ key ] = value;
			}
		});

		if ( resize ) {
			this._size();
		}
		if ( this.uiDialog.is( ":data(resizable)" ) ) {
			this.uiDialog.resizable( "option", resizableOptions );
		}
	},

	_setOption: function(key, value){
		var self = this,
			uiDialog = self.uiDialog;

		switch (key) {
			//handling of deprecated beforeclose (vs beforeClose) option
			//Ticket #4669 http://dev.jqueryui.com/ticket/4669
			//TODO: remove in 1.9pre
			case "beforeclose":
				key = "beforeClose";
				break;
			case "buttons":
				self._createButtons(value);
				break;
			case "closeText":
				// ensure that we always pass a string
				self.uiDialogTitlebarCloseText.text("" + value);
				break;
			case "dialogClass":
				uiDialog
					.removeClass(self.options.dialogClass)
					.addClass(uiDialogClasses + value);
				break;
			case "disabled":
				if (value) {
					uiDialog.addClass('ui-dialog-disabled');
				} else {
					uiDialog.removeClass('ui-dialog-disabled');
				}
				break;
			case "draggable":
				var isDraggable = uiDialog.is( ":data(draggable)" );
				if ( isDraggable && !value ) {
					uiDialog.draggable( "destroy" );
				}
				
				if ( !isDraggable && value ) {
					self._makeDraggable();
				}
				break;
			case "position":
				self._position(value);
				break;
			case "resizable":
				// currently resizable, becoming non-resizable
				var isResizable = uiDialog.is( ":data(resizable)" );
				if (isResizable && !value) {
					uiDialog.resizable('destroy');
				}

				// currently resizable, changing handles
				if (isResizable && typeof value === 'string') {
					uiDialog.resizable('option', 'handles', value);
				}

				// currently non-resizable, becoming resizable
				if (!isResizable && value !== false) {
					self._makeResizable(value);
				}
				break;
			case "title":
				// convert whatever was passed in o a string, for html() to not throw up
				$(".ui-dialog-title", self.uiDialogTitlebar).html("" + (value || '&#160;'));
				break;
		}

		$.Widget.prototype._setOption.apply(self, arguments);
	},

	_size: function() {
		/* If the user has resized the dialog, the .ui-dialog and .ui-dialog-content
		 * divs will both have width and height set, so we need to reset them
		 */
		var options = this.options,
			nonContentHeight,
			minContentHeight,
			isVisible = this.uiDialog.is( ":visible" );

		// reset content sizing
		this.element.show().css({
			width: 'auto',
			minHeight: 0,
			height: 0
		});

		if (options.minWidth > options.width) {
			options.width = options.minWidth;
		}

		// reset wrapper sizing
		// determine the height of all the non-content elements
		nonContentHeight = this.uiDialog.css({
				height: 'auto',
				width: options.width
			})
			.height();
		minContentHeight = Math.max( 0, options.minHeight - nonContentHeight );
		
		if ( options.height === "auto" ) {
			// only needed for IE6 support
			if ( $.support.minHeight ) {
				this.element.css({
					minHeight: minContentHeight,
					height: "auto"
				});
			} else {
				this.uiDialog.show();
				var autoHeight = this.element.css( "height", "auto" ).height();
				if ( !isVisible ) {
					this.uiDialog.hide();
				}
				this.element.height( Math.max( autoHeight, minContentHeight ) );
			}
		} else {
			this.element.height( Math.max( options.height - nonContentHeight, 0 ) );
		}

		if (this.uiDialog.is(':data(resizable)')) {
			this.uiDialog.resizable('option', 'minHeight', this._minHeight());
		}
	}
});

$.extend($.ui.dialog, {
	version: "1.8.22",

	uuid: 0,
	maxZ: 0,

	getTitleId: function($el) {
		var id = $el.attr('id');
		if (!id) {
			this.uuid += 1;
			id = this.uuid;
		}
		return 'ui-dialog-title-' + id;
	},

	overlay: function(dialog) {
		this.$el = $.ui.dialog.overlay.create(dialog);
	}
});

$.extend($.ui.dialog.overlay, {
	instances: [],
	// reuse old instances due to IE memory leak with alpha transparency (see #5185)
	oldInstances: [],
	maxZ: 0,
	events: $.map('focus,mousedown,mouseup,keydown,keypress,click'.split(','),
		function(event) { return event + '.dialog-overlay'; }).join(' '),
	create: function(dialog) {
		if (this.instances.length === 0) {
			// prevent use of anchors and inputs
			// we use a setTimeout in case the overlay is created from an
			// event that we're going to be cancelling (see #2804)
			setTimeout(function() {
				// handle $(el).dialog().dialog('close') (see #4065)
				if ($.ui.dialog.overlay.instances.length) {
					$(document).bind($.ui.dialog.overlay.events, function(event) {
						// stop events if the z-index of the target is < the z-index of the overlay
						// we cannot return true when we don't want to cancel the event (#3523)
						if ($(event.target).zIndex() < $.ui.dialog.overlay.maxZ) {
							return false;
						}
					});
				}
			}, 1);

			// allow closing by pressing the escape key
			$(document).bind('keydown.dialog-overlay', function(event) {
				if (dialog.options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode &&
					event.keyCode === $.ui.keyCode.ESCAPE) {
					
					dialog.close(event);
					event.preventDefault();
				}
			});

			// handle window resize
			$(window).bind('resize.dialog-overlay', $.ui.dialog.overlay.resize);
		}

		var $el = (this.oldInstances.pop() || $('<div></div>').addClass('ui-widget-overlay'))
			.appendTo(document.body)
			.css({
				width: this.width(),
				height: this.height()
			});

		if ($.fn.bgiframe) {
			$el.bgiframe();
		}

		this.instances.push($el);
		return $el;
	},

	destroy: function($el) {
		var indexOf = $.inArray($el, this.instances);
		if (indexOf != -1){
			this.oldInstances.push(this.instances.splice(indexOf, 1)[0]);
		}

		if (this.instances.length === 0) {
			$([document, window]).unbind('.dialog-overlay');
		}

		$el.remove();
		
		// adjust the maxZ to allow other modal dialogs to continue to work (see #4309)
		var maxZ = 0;
		$.each(this.instances, function() {
			maxZ = Math.max(maxZ, this.css('z-index'));
		});
		this.maxZ = maxZ;
	},

	height: function() {
		var scrollHeight,
			offsetHeight;
		// handle IE 6
		if ($.browser.msie && $.browser.version < 7) {
			scrollHeight = Math.max(
				document.documentElement.scrollHeight,
				document.body.scrollHeight
			);
			offsetHeight = Math.max(
				document.documentElement.offsetHeight,
				document.body.offsetHeight
			);

			if (scrollHeight < offsetHeight) {
				return $(window).height() + 'px';
			} else {
				return scrollHeight + 'px';
			}
		// handle "good" browsers
		} else {
			return $(document).height() + 'px';
		}
	},

	width: function() {
		var scrollWidth,
			offsetWidth;
		// handle IE
		if ( $.browser.msie ) {
			scrollWidth = Math.max(
				document.documentElement.scrollWidth,
				document.body.scrollWidth
			);
			offsetWidth = Math.max(
				document.documentElement.offsetWidth,
				document.body.offsetWidth
			);

			if (scrollWidth < offsetWidth) {
				return $(window).width() + 'px';
			} else {
				return scrollWidth + 'px';
			}
		// handle "good" browsers
		} else {
			return $(document).width() + 'px';
		}
	},

	resize: function() {
		/* If the dialog is draggable and the user drags it past the
		 * right edge of the window, the document becomes wider so we
		 * need to stretch the overlay. If the user then drags the
		 * dialog back to the left, the document will become narrower,
		 * so we need to shrink the overlay to the appropriate size.
		 * This is handled by shrinking the overlay before setting it
		 * to the full document size.
		 */
		var $overlays = $([]);
		$.each($.ui.dialog.overlay.instances, function() {
			$overlays = $overlays.add(this);
		});

		$overlays.css({
			width: 0,
			height: 0
		}).css({
			width: $.ui.dialog.overlay.width(),
			height: $.ui.dialog.overlay.height()
		});
	}
});

$.extend($.ui.dialog.overlay.prototype, {
	destroy: function() {
		$.ui.dialog.overlay.destroy(this.$el);
	}
});

}(jQuery));

(function( $, undefined ) {

$.ui = $.ui || {};

var horizontalPositions = /left|center|right/,
	verticalPositions = /top|center|bottom/,
	center = "center",
	support = {},
	_position = $.fn.position,
	_offset = $.fn.offset;

$.fn.position = function( options ) {
	if ( !options || !options.of ) {
		return _position.apply( this, arguments );
	}

	// make a copy, we don't want to modify arguments
	options = $.extend( {}, options );

	var target = $( options.of ),
		targetElem = target[0],
		collision = ( options.collision || "flip" ).split( " " ),
		offset = options.offset ? options.offset.split( " " ) : [ 0, 0 ],
		targetWidth,
		targetHeight,
		basePosition;

	if ( targetElem.nodeType === 9 ) {
		targetWidth = target.width();
		targetHeight = target.height();
		basePosition = { top: 0, left: 0 };
	// TODO: use $.isWindow() in 1.9
	} else if ( targetElem.setTimeout ) {
		targetWidth = target.width();
		targetHeight = target.height();
		basePosition = { top: target.scrollTop(), left: target.scrollLeft() };
	} else if ( targetElem.preventDefault ) {
		// force left top to allow flipping
		options.at = "left top";
		targetWidth = targetHeight = 0;
		basePosition = { top: options.of.pageY, left: options.of.pageX };
	} else {
		targetWidth = target.outerWidth();
		targetHeight = target.outerHeight();
		basePosition = target.offset();
	}

	// force my and at to have valid horizontal and veritcal positions
	// if a value is missing or invalid, it will be converted to center 
	$.each( [ "my", "at" ], function() {
		var pos = ( options[this] || "" ).split( " " );
		if ( pos.length === 1) {
			pos = horizontalPositions.test( pos[0] ) ?
				pos.concat( [center] ) :
				verticalPositions.test( pos[0] ) ?
					[ center ].concat( pos ) :
					[ center, center ];
		}
		pos[ 0 ] = horizontalPositions.test( pos[0] ) ? pos[ 0 ] : center;
		pos[ 1 ] = verticalPositions.test( pos[1] ) ? pos[ 1 ] : center;
		options[ this ] = pos;
	});

	// normalize collision option
	if ( collision.length === 1 ) {
		collision[ 1 ] = collision[ 0 ];
	}

	// normalize offset option
	offset[ 0 ] = parseInt( offset[0], 10 ) || 0;
	if ( offset.length === 1 ) {
		offset[ 1 ] = offset[ 0 ];
	}
	offset[ 1 ] = parseInt( offset[1], 10 ) || 0;

	if ( options.at[0] === "right" ) {
		basePosition.left += targetWidth;
	} else if ( options.at[0] === center ) {
		basePosition.left += targetWidth / 2;
	}

	if ( options.at[1] === "bottom" ) {
		basePosition.top += targetHeight;
	} else if ( options.at[1] === center ) {
		basePosition.top += targetHeight / 2;
	}

	basePosition.left += offset[ 0 ];
	basePosition.top += offset[ 1 ];

	return this.each(function() {
		var elem = $( this ),
			elemWidth = elem.outerWidth(),
			elemHeight = elem.outerHeight(),
			marginLeft = parseInt( $.curCSS( this, "marginLeft", true ) ) || 0,
			marginTop = parseInt( $.curCSS( this, "marginTop", true ) ) || 0,
			collisionWidth = elemWidth + marginLeft +
				( parseInt( $.curCSS( this, "marginRight", true ) ) || 0 ),
			collisionHeight = elemHeight + marginTop +
				( parseInt( $.curCSS( this, "marginBottom", true ) ) || 0 ),
			position = $.extend( {}, basePosition ),
			collisionPosition;

		if ( options.my[0] === "right" ) {
			position.left -= elemWidth;
		} else if ( options.my[0] === center ) {
			position.left -= elemWidth / 2;
		}

		if ( options.my[1] === "bottom" ) {
			position.top -= elemHeight;
		} else if ( options.my[1] === center ) {
			position.top -= elemHeight / 2;
		}

		// prevent fractions if jQuery version doesn't support them (see #5280)
		if ( !support.fractions ) {
			position.left = Math.round( position.left );
			position.top = Math.round( position.top );
		}

		collisionPosition = {
			left: position.left - marginLeft,
			top: position.top - marginTop
		};

		$.each( [ "left", "top" ], function( i, dir ) {
			if ( $.ui.position[ collision[i] ] ) {
				$.ui.position[ collision[i] ][ dir ]( position, {
					targetWidth: targetWidth,
					targetHeight: targetHeight,
					elemWidth: elemWidth,
					elemHeight: elemHeight,
					collisionPosition: collisionPosition,
					collisionWidth: collisionWidth,
					collisionHeight: collisionHeight,
					offset: offset,
					my: options.my,
					at: options.at
				});
			}
		});

		if ( $.fn.bgiframe ) {
			elem.bgiframe();
		}
		elem.offset( $.extend( position, { using: options.using } ) );
	});
};

$.ui.position = {
	fit: {
		left: function( position, data ) {
			var win = $( window ),
				over = data.collisionPosition.left + data.collisionWidth - win.width() - win.scrollLeft();
			position.left = over > 0 ? position.left - over : Math.max( position.left - data.collisionPosition.left, position.left );
		},
		top: function( position, data ) {
			var win = $( window ),
				over = data.collisionPosition.top + data.collisionHeight - win.height() - win.scrollTop();
			position.top = over > 0 ? position.top - over : Math.max( position.top - data.collisionPosition.top, position.top );
		}
	},

	flip: {
		left: function( position, data ) {
			if ( data.at[0] === center ) {
				return;
			}
			var win = $( window ),
				over = data.collisionPosition.left + data.collisionWidth - win.width() - win.scrollLeft(),
				myOffset = data.my[ 0 ] === "left" ?
					-data.elemWidth :
					data.my[ 0 ] === "right" ?
						data.elemWidth :
						0,
				atOffset = data.at[ 0 ] === "left" ?
					data.targetWidth :
					-data.targetWidth,
				offset = -2 * data.offset[ 0 ];
			position.left += data.collisionPosition.left < 0 ?
				myOffset + atOffset + offset :
				over > 0 ?
					myOffset + atOffset + offset :
					0;
		},
		top: function( position, data ) {
			if ( data.at[1] === center ) {
				return;
			}
			var win = $( window ),
				over = data.collisionPosition.top + data.collisionHeight - win.height() - win.scrollTop(),
				myOffset = data.my[ 1 ] === "top" ?
					-data.elemHeight :
					data.my[ 1 ] === "bottom" ?
						data.elemHeight :
						0,
				atOffset = data.at[ 1 ] === "top" ?
					data.targetHeight :
					-data.targetHeight,
				offset = -2 * data.offset[ 1 ];
			position.top += data.collisionPosition.top < 0 ?
				myOffset + atOffset + offset :
				over > 0 ?
					myOffset + atOffset + offset :
					0;
		}
	}
};

// offset setter from jQuery 1.4
if ( !$.offset.setOffset ) {
	$.offset.setOffset = function( elem, options ) {
		// set position first, in-case top/left are set even on static elem
		if ( /static/.test( $.curCSS( elem, "position" ) ) ) {
			elem.style.position = "relative";
		}
		var curElem   = $( elem ),
			curOffset = curElem.offset(),
			curTop    = parseInt( $.curCSS( elem, "top",  true ), 10 ) || 0,
			curLeft   = parseInt( $.curCSS( elem, "left", true ), 10)  || 0,
			props     = {
				top:  (options.top  - curOffset.top)  + curTop,
				left: (options.left - curOffset.left) + curLeft
			};
		
		if ( 'using' in options ) {
			options.using.call( elem, props );
		} else {
			curElem.css( props );
		}
	};

	$.fn.offset = function( options ) {
		var elem = this[ 0 ];
		if ( !elem || !elem.ownerDocument ) { return null; }
		if ( options ) {
			if ( $.isFunction( options ) ) {
				return this.each(function( i ) {
					$( this ).offset( options.call( this, i, $( this ).offset() ) );
				});
			}
			return this.each(function() {
				$.offset.setOffset( this, options );
			});
		}
		return _offset.call( this );
	};
}

// fraction support test (older versions of jQuery don't support fractions)
(function () {
	var body = document.getElementsByTagName( "body" )[ 0 ], 
		div = document.createElement( "div" ),
		testElement, testElementParent, testElementStyle, offset, offsetTotal;

	//Create a "fake body" for testing based on method used in jQuery.support
	testElement = document.createElement( body ? "div" : "body" );
	testElementStyle = {
		visibility: "hidden",
		width: 0,
		height: 0,
		border: 0,
		margin: 0,
		background: "none"
	};
	if ( body ) {
		$.extend( testElementStyle, {
			position: "absolute",
			left: "-1000px",
			top: "-1000px"
		});
	}
	for ( var i in testElementStyle ) {
		testElement.style[ i ] = testElementStyle[ i ];
	}
	testElement.appendChild( div );
	testElementParent = body || document.documentElement;
	testElementParent.insertBefore( testElement, testElementParent.firstChild );

	div.style.cssText = "position: absolute; left: 10.7432222px; top: 10.432325px; height: 30px; width: 201px;";

	offset = $( div ).offset( function( _, offset ) {
		return offset;
	}).offset();

	testElement.innerHTML = "";
	testElementParent.removeChild( testElement );

	offsetTotal = offset.top + offset.left + ( body ? 2000 : 0 );
	support.fractions = offsetTotal > 21 && offsetTotal < 22;
})();

}( jQuery ));

(function( $, undefined ) {

$.widget( "ui.progressbar", {
	options: {
		value: 0,
		max: 100
	},

	min: 0,

	_create: function() {
		this.element
			.addClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" )
			.attr({
				role: "progressbar",
				"aria-valuemin": this.min,
				"aria-valuemax": this.options.max,
				"aria-valuenow": this._value()
			});

		this.valueDiv = $( "<div class='ui-progressbar-value ui-widget-header ui-corner-left'></div>" )
			.appendTo( this.element );

		this.oldValue = this._value();
		this._refreshValue();
	},

	destroy: function() {
		this.element
			.removeClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" )
			.removeAttr( "role" )
			.removeAttr( "aria-valuemin" )
			.removeAttr( "aria-valuemax" )
			.removeAttr( "aria-valuenow" );

		this.valueDiv.remove();

		$.Widget.prototype.destroy.apply( this, arguments );
	},

	value: function( newValue ) {
		if ( newValue === undefined ) {
			return this._value();
		}

		this._setOption( "value", newValue );
		return this;
	},

	_setOption: function( key, value ) {
		if ( key === "value" ) {
			this.options.value = value;
			this._refreshValue();
			if ( this._value() === this.options.max ) {
				this._trigger( "complete" );
			}
		}

		$.Widget.prototype._setOption.apply( this, arguments );
	},

	_value: function() {
		var val = this.options.value;
		// normalize invalid value
		if ( typeof val !== "number" ) {
			val = 0;
		}
		return Math.min( this.options.max, Math.max( this.min, val ) );
	},

	_percentage: function() {
		return 100 * this._value() / this.options.max;
	},

	_refreshValue: function() {
		var value = this.value();
		var percentage = this._percentage();

		if ( this.oldValue !== value ) {
			this.oldValue = value;
			this._trigger( "change" );
		}

		this.valueDiv
			.toggle( value > this.min )
			.toggleClass( "ui-corner-right", value === this.options.max )
			.width( percentage.toFixed(0) + "%" );
		this.element.attr( "aria-valuenow", value );
	}
});

$.extend( $.ui.progressbar, {
	version: "1.8.22"
});

})( jQuery );

(function( $, undefined ) {

// number of pages in a slider
// (how many times can you page up/down to go through the whole range)
var numPages = 5;

$.widget( "ui.slider", $.ui.mouse, {

	widgetEventPrefix: "slide",

	options: {
		animate: false,
		distance: 0,
		max: 100,
		min: 0,
		orientation: "horizontal",
		range: false,
		step: 1,
		value: 0,
		values: null
	},

	_create: function() {
		var self = this,
			o = this.options,
			existingHandles = this.element.find( ".ui-slider-handle" ).addClass( "ui-state-default ui-corner-all" ),
			handle = "<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>",
			handleCount = ( o.values && o.values.length ) || 1,
			handles = [];

		this._keySliding = false;
		this._mouseSliding = false;
		this._animateOff = true;
		this._handleIndex = null;
		this._detectOrientation();
		this._mouseInit();

		this.element
			.addClass( "ui-slider" +
				" ui-slider-" + this.orientation +
				" ui-widget" +
				" ui-widget-content" +
				" ui-corner-all" +
				( o.disabled ? " ui-slider-disabled ui-disabled" : "" ) );

		this.range = $([]);

		if ( o.range ) {
			if ( o.range === true ) {
				if ( !o.values ) {
					o.values = [ this._valueMin(), this._valueMin() ];
				}
				if ( o.values.length && o.values.length !== 2 ) {
					o.values = [ o.values[0], o.values[0] ];
				}
			}

			this.range = $( "<div></div>" )
				.appendTo( this.element )
				.addClass( "ui-slider-range" +
				// note: this isn't the most fittingly semantic framework class for this element,
				// but worked best visually with a variety of themes
				" ui-widget-header" + 
				( ( o.range === "min" || o.range === "max" ) ? " ui-slider-range-" + o.range : "" ) );
		}

		for ( var i = existingHandles.length; i < handleCount; i += 1 ) {
			handles.push( handle );
		}

		this.handles = existingHandles.add( $( handles.join( "" ) ).appendTo( self.element ) );

		this.handle = this.handles.eq( 0 );

		this.handles.add( this.range ).filter( "a" )
			.click(function( event ) {
				event.preventDefault();
			})
			.hover(function() {
				if ( !o.disabled ) {
					$( this ).addClass( "ui-state-hover" );
				}
			}, function() {
				$( this ).removeClass( "ui-state-hover" );
			})
			.focus(function() {
				if ( !o.disabled ) {
					$( ".ui-slider .ui-state-focus" ).removeClass( "ui-state-focus" );
					$( this ).addClass( "ui-state-focus" );
				} else {
					$( this ).blur();
				}
			})
			.blur(function() {
				$( this ).removeClass( "ui-state-focus" );
			});

		this.handles.each(function( i ) {
			$( this ).data( "index.ui-slider-handle", i );
		});

		this.handles
			.keydown(function( event ) {
				var index = $( this ).data( "index.ui-slider-handle" ),
					allowed,
					curVal,
					newVal,
					step;
	
				if ( self.options.disabled ) {
					return;
				}
	
				switch ( event.keyCode ) {
					case $.ui.keyCode.HOME:
					case $.ui.keyCode.END:
					case $.ui.keyCode.PAGE_UP:
					case $.ui.keyCode.PAGE_DOWN:
					case $.ui.keyCode.UP:
					case $.ui.keyCode.RIGHT:
					case $.ui.keyCode.DOWN:
					case $.ui.keyCode.LEFT:
						event.preventDefault();
						if ( !self._keySliding ) {
							self._keySliding = true;
							$( this ).addClass( "ui-state-active" );
							allowed = self._start( event, index );
							if ( allowed === false ) {
								return;
							}
						}
						break;
				}
	
				step = self.options.step;
				if ( self.options.values && self.options.values.length ) {
					curVal = newVal = self.values( index );
				} else {
					curVal = newVal = self.value();
				}
	
				switch ( event.keyCode ) {
					case $.ui.keyCode.HOME:
						newVal = self._valueMin();
						break;
					case $.ui.keyCode.END:
						newVal = self._valueMax();
						break;
					case $.ui.keyCode.PAGE_UP:
						newVal = self._trimAlignValue( curVal + ( (self._valueMax() - self._valueMin()) / numPages ) );
						break;
					case $.ui.keyCode.PAGE_DOWN:
						newVal = self._trimAlignValue( curVal - ( (self._valueMax() - self._valueMin()) / numPages ) );
						break;
					case $.ui.keyCode.UP:
					case $.ui.keyCode.RIGHT:
						if ( curVal === self._valueMax() ) {
							return;
						}
						newVal = self._trimAlignValue( curVal + step );
						break;
					case $.ui.keyCode.DOWN:
					case $.ui.keyCode.LEFT:
						if ( curVal === self._valueMin() ) {
							return;
						}
						newVal = self._trimAlignValue( curVal - step );
						break;
				}
	
				self._slide( event, index, newVal );
			})
			.keyup(function( event ) {
				var index = $( this ).data( "index.ui-slider-handle" );
	
				if ( self._keySliding ) {
					self._keySliding = false;
					self._stop( event, index );
					self._change( event, index );
					$( this ).removeClass( "ui-state-active" );
				}
	
			});

		this._refreshValue();

		this._animateOff = false;
	},

	destroy: function() {
		this.handles.remove();
		this.range.remove();

		this.element
			.removeClass( "ui-slider" +
				" ui-slider-horizontal" +
				" ui-slider-vertical" +
				" ui-slider-disabled" +
				" ui-widget" +
				" ui-widget-content" +
				" ui-corner-all" )
			.removeData( "slider" )
			.unbind( ".slider" );

		this._mouseDestroy();

		return this;
	},

	_mouseCapture: function( event ) {
		var o = this.options,
			position,
			normValue,
			distance,
			closestHandle,
			self,
			index,
			allowed,
			offset,
			mouseOverHandle;

		if ( o.disabled ) {
			return false;
		}

		this.elementSize = {
			width: this.element.outerWidth(),
			height: this.element.outerHeight()
		};
		this.elementOffset = this.element.offset();

		position = { x: event.pageX, y: event.pageY };
		normValue = this._normValueFromMouse( position );
		distance = this._valueMax() - this._valueMin() + 1;
		self = this;
		this.handles.each(function( i ) {
			var thisDistance = Math.abs( normValue - self.values(i) );
			if ( distance > thisDistance ) {
				distance = thisDistance;
				closestHandle = $( this );
				index = i;
			}
		});

		// workaround for bug #3736 (if both handles of a range are at 0,
		// the first is always used as the one with least distance,
		// and moving it is obviously prevented by preventing negative ranges)
		if( o.range === true && this.values(1) === o.min ) {
			index += 1;
			closestHandle = $( this.handles[index] );
		}

		allowed = this._start( event, index );
		if ( allowed === false ) {
			return false;
		}
		this._mouseSliding = true;

		self._handleIndex = index;

		closestHandle
			.addClass( "ui-state-active" )
			.focus();
		
		offset = closestHandle.offset();
		mouseOverHandle = !$( event.target ).parents().andSelf().is( ".ui-slider-handle" );
		this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : {
			left: event.pageX - offset.left - ( closestHandle.width() / 2 ),
			top: event.pageY - offset.top -
				( closestHandle.height() / 2 ) -
				( parseInt( closestHandle.css("borderTopWidth"), 10 ) || 0 ) -
				( parseInt( closestHandle.css("borderBottomWidth"), 10 ) || 0) +
				( parseInt( closestHandle.css("marginTop"), 10 ) || 0)
		};

		if ( !this.handles.hasClass( "ui-state-hover" ) ) {
			this._slide( event, index, normValue );
		}
		this._animateOff = true;
		return true;
	},

	_mouseStart: function( event ) {
		return true;
	},

	_mouseDrag: function( event ) {
		var position = { x: event.pageX, y: event.pageY },
			normValue = this._normValueFromMouse( position );
		
		this._slide( event, this._handleIndex, normValue );

		return false;
	},

	_mouseStop: function( event ) {
		this.handles.removeClass( "ui-state-active" );
		this._mouseSliding = false;

		this._stop( event, this._handleIndex );
		this._change( event, this._handleIndex );

		this._handleIndex = null;
		this._clickOffset = null;
		this._animateOff = false;

		return false;
	},
	
	_detectOrientation: function() {
		this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal";
	},

	_normValueFromMouse: function( position ) {
		var pixelTotal,
			pixelMouse,
			percentMouse,
			valueTotal,
			valueMouse;

		if ( this.orientation === "horizontal" ) {
			pixelTotal = this.elementSize.width;
			pixelMouse = position.x - this.elementOffset.left - ( this._clickOffset ? this._clickOffset.left : 0 );
		} else {
			pixelTotal = this.elementSize.height;
			pixelMouse = position.y - this.elementOffset.top - ( this._clickOffset ? this._clickOffset.top : 0 );
		}

		percentMouse = ( pixelMouse / pixelTotal );
		if ( percentMouse > 1 ) {
			percentMouse = 1;
		}
		if ( percentMouse < 0 ) {
			percentMouse = 0;
		}
		if ( this.orientation === "vertical" ) {
			percentMouse = 1 - percentMouse;
		}

		valueTotal = this._valueMax() - this._valueMin();
		valueMouse = this._valueMin() + percentMouse * valueTotal;

		return this._trimAlignValue( valueMouse );
	},

	_start: function( event, index ) {
		var uiHash = {
			handle: this.handles[ index ],
			value: this.value()
		};
		if ( this.options.values && this.options.values.length ) {
			uiHash.value = this.values( index );
			uiHash.values = this.values();
		}
		return this._trigger( "start", event, uiHash );
	},

	_slide: function( event, index, newVal ) {
		var otherVal,
			newValues,
			allowed;

		if ( this.options.values && this.options.values.length ) {
			otherVal = this.values( index ? 0 : 1 );

			if ( ( this.options.values.length === 2 && this.options.range === true ) && 
					( ( index === 0 && newVal > otherVal) || ( index === 1 && newVal < otherVal ) )
				) {
				newVal = otherVal;
			}

			if ( newVal !== this.values( index ) ) {
				newValues = this.values();
				newValues[ index ] = newVal;
				// A slide can be canceled by returning false from the slide callback
				allowed = this._trigger( "slide", event, {
					handle: this.handles[ index ],
					value: newVal,
					values: newValues
				} );
				otherVal = this.values( index ? 0 : 1 );
				if ( allowed !== false ) {
					this.values( index, newVal, true );
				}
			}
		} else {
			if ( newVal !== this.value() ) {
				// A slide can be canceled by returning false from the slide callback
				allowed = this._trigger( "slide", event, {
					handle: this.handles[ index ],
					value: newVal
				} );
				if ( allowed !== false ) {
					this.value( newVal );
				}
			}
		}
	},

	_stop: function( event, index ) {
		var uiHash = {
			handle: this.handles[ index ],
			value: this.value()
		};
		if ( this.options.values && this.options.values.length ) {
			uiHash.value = this.values( index );
			uiHash.values = this.values();
		}

		this._trigger( "stop", event, uiHash );
	},

	_change: function( event, index ) {
		if ( !this._keySliding && !this._mouseSliding ) {
			var uiHash = {
				handle: this.handles[ index ],
				value: this.value()
			};
			if ( this.options.values && this.options.values.length ) {
				uiHash.value = this.values( index );
				uiHash.values = this.values();
			}

			this._trigger( "change", event, uiHash );
		}
	},

	value: function( newValue ) {
		if ( arguments.length ) {
			this.options.value = this._trimAlignValue( newValue );
			this._refreshValue();
			this._change( null, 0 );
			return;
		}

		return this._value();
	},

	values: function( index, newValue ) {
		var vals,
			newValues,
			i;

		if ( arguments.length > 1 ) {
			this.options.values[ index ] = this._trimAlignValue( newValue );
			this._refreshValue();
			this._change( null, index );
			return;
		}

		if ( arguments.length ) {
			if ( $.isArray( arguments[ 0 ] ) ) {
				vals = this.options.values;
				newValues = arguments[ 0 ];
				for ( i = 0; i < vals.length; i += 1 ) {
					vals[ i ] = this._trimAlignValue( newValues[ i ] );
					this._change( null, i );
				}
				this._refreshValue();
			} else {
				if ( this.options.values && this.options.values.length ) {
					return this._values( index );
				} else {
					return this.value();
				}
			}
		} else {
			return this._values();
		}
	},

	_setOption: function( key, value ) {
		var i,
			valsLength = 0;

		if ( $.isArray( this.options.values ) ) {
			valsLength = this.options.values.length;
		}

		$.Widget.prototype._setOption.apply( this, arguments );

		switch ( key ) {
			case "disabled":
				if ( value ) {
					this.handles.filter( ".ui-state-focus" ).blur();
					this.handles.removeClass( "ui-state-hover" );
					this.handles.propAttr( "disabled", true );
					this.element.addClass( "ui-disabled" );
				} else {
					this.handles.propAttr( "disabled", false );
					this.element.removeClass( "ui-disabled" );
				}
				break;
			case "orientation":
				this._detectOrientation();
				this.element
					.removeClass( "ui-slider-horizontal ui-slider-vertical" )
					.addClass( "ui-slider-" + this.orientation );
				this._refreshValue();
				break;
			case "value":
				this._animateOff = true;
				this._refreshValue();
				this._change( null, 0 );
				this._animateOff = false;
				break;
			case "values":
				this._animateOff = true;
				this._refreshValue();
				for ( i = 0; i < valsLength; i += 1 ) {
					this._change( null, i );
				}
				this._animateOff = false;
				break;
		}
	},

	//internal value getter
	// _value() returns value trimmed by min and max, aligned by step
	_value: function() {
		var val = this.options.value;
		val = this._trimAlignValue( val );

		return val;
	},

	//internal values getter
	// _values() returns array of values trimmed by min and max, aligned by step
	// _values( index ) returns single value trimmed by min and max, aligned by step
	_values: function( index ) {
		var val,
			vals,
			i;

		if ( arguments.length ) {
			val = this.options.values[ index ];
			val = this._trimAlignValue( val );

			return val;
		} else {
			// .slice() creates a copy of the array
			// this copy gets trimmed by min and max and then returned
			vals = this.options.values.slice();
			for ( i = 0; i < vals.length; i+= 1) {
				vals[ i ] = this._trimAlignValue( vals[ i ] );
			}

			return vals;
		}
	},
	
	// returns the step-aligned value that val is closest to, between (inclusive) min and max
	_trimAlignValue: function( val ) {
		if ( val <= this._valueMin() ) {
			return this._valueMin();
		}
		if ( val >= this._valueMax() ) {
			return this._valueMax();
		}
		var step = ( this.options.step > 0 ) ? this.options.step : 1,
			valModStep = (val - this._valueMin()) % step,
			alignValue = val - valModStep;

		if ( Math.abs(valModStep) * 2 >= step ) {
			alignValue += ( valModStep > 0 ) ? step : ( -step );
		}

		// Since JavaScript has problems with large floats, round
		// the final value to 5 digits after the decimal point (see #4124)
		return parseFloat( alignValue.toFixed(5) );
	},

	_valueMin: function() {
		return this.options.min;
	},

	_valueMax: function() {
		return this.options.max;
	},
	
	_refreshValue: function() {
		var oRange = this.options.range,
			o = this.options,
			self = this,
			animate = ( !this._animateOff ) ? o.animate : false,
			valPercent,
			_set = {},
			lastValPercent,
			value,
			valueMin,
			valueMax;

		if ( this.options.values && this.options.values.length ) {
			this.handles.each(function( i, j ) {
				valPercent = ( self.values(i) - self._valueMin() ) / ( self._valueMax() - self._valueMin() ) * 100;
				_set[ self.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
				$( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
				if ( self.options.range === true ) {
					if ( self.orientation === "horizontal" ) {
						if ( i === 0 ) {
							self.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { left: valPercent + "%" }, o.animate );
						}
						if ( i === 1 ) {
							self.range[ animate ? "animate" : "css" ]( { width: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
						}
					} else {
						if ( i === 0 ) {
							self.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { bottom: ( valPercent ) + "%" }, o.animate );
						}
						if ( i === 1 ) {
							self.range[ animate ? "animate" : "css" ]( { height: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
						}
					}
				}
				lastValPercent = valPercent;
			});
		} else {
			value = this.value();
			valueMin = this._valueMin();
			valueMax = this._valueMax();
			valPercent = ( valueMax !== valueMin ) ?
					( value - valueMin ) / ( valueMax - valueMin ) * 100 :
					0;
			_set[ self.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
			this.handle.stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );

			if ( oRange === "min" && this.orientation === "horizontal" ) {
				this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { width: valPercent + "%" }, o.animate );
			}
			if ( oRange === "max" && this.orientation === "horizontal" ) {
				this.range[ animate ? "animate" : "css" ]( { width: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
			}
			if ( oRange === "min" && this.orientation === "vertical" ) {
				this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { height: valPercent + "%" }, o.animate );
			}
			if ( oRange === "max" && this.orientation === "vertical" ) {
				this.range[ animate ? "animate" : "css" ]( { height: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
			}
		}
	}

});

$.extend( $.ui.slider, {
	version: "1.8.22"
});

}(jQuery));

(function( $, undefined ) {

var tabId = 0,
	listId = 0;

function getNextTabId() {
	return ++tabId;
}

function getNextListId() {
	return ++listId;
}

$.widget( "ui.tabs", {
	options: {
		add: null,
		ajaxOptions: null,
		cache: false,
		cookie: null, // e.g. { expires: 7, path: '/', domain: 'jquery.com', secure: true }
		collapsible: false,
		disable: null,
		disabled: [],
		enable: null,
		event: "click",
		fx: null, // e.g. { height: 'toggle', opacity: 'toggle', duration: 200 }
		idPrefix: "ui-tabs-",
		load: null,
		panelTemplate: "<div></div>",
		remove: null,
		select: null,
		show: null,
		spinner: "<em>Loading&#8230;</em>",
		tabTemplate: "<li><a href='#{href}'><span>#{label}</span></a></li>"
	},

	_create: function() {
		this._tabify( true );
	},

	_setOption: function( key, value ) {
		if ( key == "selected" ) {
			if (this.options.collapsible && value == this.options.selected ) {
				return;
			}
			this.select( value );
		} else {
			this.options[ key ] = value;
			this._tabify();
		}
	},

	_tabId: function( a ) {
		return a.title && a.title.replace( /\s/g, "_" ).replace( /[^\w\u00c0-\uFFFF-]/g, "" ) ||
			this.options.idPrefix + getNextTabId();
	},

	_sanitizeSelector: function( hash ) {
		// we need this because an id may contain a ":"
		return hash.replace( /:/g, "\\:" );
	},

	_cookie: function() {
		var cookie = this.cookie ||
			( this.cookie = this.options.cookie.name || "ui-tabs-" + getNextListId() );
		return $.cookie.apply( null, [ cookie ].concat( $.makeArray( arguments ) ) );
	},

	_ui: function( tab, panel ) {
		return {
			tab: tab,
			panel: panel,
			index: this.anchors.index( tab )
		};
	},

	_cleanup: function() {
		// restore all former loading tabs labels
		this.lis.filter( ".ui-state-processing" )
			.removeClass( "ui-state-processing" )
			.find( "span:data(label.tabs)" )
				.each(function() {
					var el = $( this );
					el.html( el.data( "label.tabs" ) ).removeData( "label.tabs" );
				});
	},

	_tabify: function( init ) {
		var self = this,
			o = this.options,
			fragmentId = /^#.+/; // Safari 2 reports '#' for an empty hash

		this.list = this.element.find( "ol,ul" ).eq( 0 );
		this.lis = $( " > li:has(a[href])", this.list );
		this.anchors = this.lis.map(function() {
			return $( "a", this )[ 0 ];
		});
		this.panels = $( [] );

		this.anchors.each(function( i, a ) {
			var href = $( a ).attr( "href" );
			// For dynamically created HTML that contains a hash as href IE < 8 expands
			// such href to the full page url with hash and then misinterprets tab as ajax.
			// Same consideration applies for an added tab with a fragment identifier
			// since a[href=#fragment-identifier] does unexpectedly not match.
			// Thus normalize href attribute...
			var hrefBase = href.split( "#" )[ 0 ],
				baseEl;
			if ( hrefBase && ( hrefBase === location.toString().split( "#" )[ 0 ] ||
					( baseEl = $( "base" )[ 0 ]) && hrefBase === baseEl.href ) ) {
				href = a.hash;
				a.href = href;
			}

			// inline tab
			if ( fragmentId.test( href ) ) {
				self.panels = self.panels.add( self.element.find( self._sanitizeSelector( href ) ) );
			// remote tab
			// prevent loading the page itself if href is just "#"
			} else if ( href && href !== "#" ) {
				// required for restore on destroy
				$.data( a, "href.tabs", href );

				// TODO until #3808 is fixed strip fragment identifier from url
				// (IE fails to load from such url)
				$.data( a, "load.tabs", href.replace( /#.*$/, "" ) );

				var id = self._tabId( a );
				a.href = "#" + id;
				var $panel = self.element.find( "#" + id );
				if ( !$panel.length ) {
					$panel = $( o.panelTemplate )
						.attr( "id", id )
						.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
						.insertAfter( self.panels[ i - 1 ] || self.list );
					$panel.data( "destroy.tabs", true );
				}
				self.panels = self.panels.add( $panel );
			// invalid tab href
			} else {
				o.disabled.push( i );
			}
		});

		// initialization from scratch
		if ( init ) {
			// attach necessary classes for styling
			this.element.addClass( "ui-tabs ui-widget ui-widget-content ui-corner-all" );
			this.list.addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" );
			this.lis.addClass( "ui-state-default ui-corner-top" );
			this.panels.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" );

			// Selected tab
			// use "selected" option or try to retrieve:
			// 1. from fragment identifier in url
			// 2. from cookie
			// 3. from selected class attribute on <li>
			if ( o.selected === undefined ) {
				if ( location.hash ) {
					this.anchors.each(function( i, a ) {
						if ( a.hash == location.hash ) {
							o.selected = i;
							return false;
						}
					});
				}
				if ( typeof o.selected !== "number" && o.cookie ) {
					o.selected = parseInt( self._cookie(), 10 );
				}
				if ( typeof o.selected !== "number" && this.lis.filter( ".ui-tabs-selected" ).length ) {
					o.selected = this.lis.index( this.lis.filter( ".ui-tabs-selected" ) );
				}
				o.selected = o.selected || ( this.lis.length ? 0 : -1 );
			} else if ( o.selected === null ) { // usage of null is deprecated, TODO remove in next release
				o.selected = -1;
			}

			// sanity check - default to first tab...
			o.selected = ( ( o.selected >= 0 && this.anchors[ o.selected ] ) || o.selected < 0 )
				? o.selected
				: 0;

			// Take disabling tabs via class attribute from HTML
			// into account and update option properly.
			// A selected tab cannot become disabled.
			o.disabled = $.unique( o.disabled.concat(
				$.map( this.lis.filter( ".ui-state-disabled" ), function( n, i ) {
					return self.lis.index( n );
				})
			) ).sort();

			if ( $.inArray( o.selected, o.disabled ) != -1 ) {
				o.disabled.splice( $.inArray( o.selected, o.disabled ), 1 );
			}

			// highlight selected tab
			this.panels.addClass( "ui-tabs-hide" );
			this.lis.removeClass( "ui-tabs-selected ui-state-active" );
			// check for length avoids error when initializing empty list
			if ( o.selected >= 0 && this.anchors.length ) {
				self.element.find( self._sanitizeSelector( self.anchors[ o.selected ].hash ) ).removeClass( "ui-tabs-hide" );
				this.lis.eq( o.selected ).addClass( "ui-tabs-selected ui-state-active" );

				// seems to be expected behavior that the show callback is fired
				self.element.queue( "tabs", function() {
					self._trigger( "show", null,
						self._ui( self.anchors[ o.selected ], self.element.find( self._sanitizeSelector( self.anchors[ o.selected ].hash ) )[ 0 ] ) );
				});

				this.load( o.selected );
			}

			// clean up to avoid memory leaks in certain versions of IE 6
			// TODO: namespace this event
			$( window ).bind( "unload", function() {
				self.lis.add( self.anchors ).unbind( ".tabs" );
				self.lis = self.anchors = self.panels = null;
			});
		// update selected after add/remove
		} else {
			o.selected = this.lis.index( this.lis.filter( ".ui-tabs-selected" ) );
		}

		// update collapsible
		// TODO: use .toggleClass()
		this.element[ o.collapsible ? "addClass" : "removeClass" ]( "ui-tabs-collapsible" );

		// set or update cookie after init and add/remove respectively
		if ( o.cookie ) {
			this._cookie( o.selected, o.cookie );
		}

		// disable tabs
		for ( var i = 0, li; ( li = this.lis[ i ] ); i++ ) {
			$( li )[ $.inArray( i, o.disabled ) != -1 &&
				// TODO: use .toggleClass()
				!$( li ).hasClass( "ui-tabs-selected" ) ? "addClass" : "removeClass" ]( "ui-state-disabled" );
		}

		// reset cache if switching from cached to not cached
		if ( o.cache === false ) {
			this.anchors.removeData( "cache.tabs" );
		}

		// remove all handlers before, tabify may run on existing tabs after add or option change
		this.lis.add( this.anchors ).unbind( ".tabs" );

		if ( o.event !== "mouseover" ) {
			var addState = function( state, el ) {
				if ( el.is( ":not(.ui-state-disabled)" ) ) {
					el.addClass( "ui-state-" + state );
				}
			};
			var removeState = function( state, el ) {
				el.removeClass( "ui-state-" + state );
			};
			this.lis.bind( "mouseover.tabs" , function() {
				addState( "hover", $( this ) );
			});
			this.lis.bind( "mouseout.tabs", function() {
				removeState( "hover", $( this ) );
			});
			this.anchors.bind( "focus.tabs", function() {
				addState( "focus", $( this ).closest( "li" ) );
			});
			this.anchors.bind( "blur.tabs", function() {
				removeState( "focus", $( this ).closest( "li" ) );
			});
		}

		// set up animations
		var hideFx, showFx;
		if ( o.fx ) {
			if ( $.isArray( o.fx ) ) {
				hideFx = o.fx[ 0 ];
				showFx = o.fx[ 1 ];
			} else {
				hideFx = showFx = o.fx;
			}
		}

		// Reset certain styles left over from animation
		// and prevent IE's ClearType bug...
		function resetStyle( $el, fx ) {
			$el.css( "display", "" );
			if ( !$.support.opacity && fx.opacity ) {
				$el[ 0 ].style.removeAttribute( "filter" );
			}
		}

		// Show a tab...
		var showTab = showFx
			? function( clicked, $show ) {
				$( clicked ).closest( "li" ).addClass( "ui-tabs-selected ui-state-active" );
				$show.hide().removeClass( "ui-tabs-hide" ) // avoid flicker that way
					.animate( showFx, showFx.duration || "normal", function() {
						resetStyle( $show, showFx );
						self._trigger( "show", null, self._ui( clicked, $show[ 0 ] ) );
					});
			}
			: function( clicked, $show ) {
				$( clicked ).closest( "li" ).addClass( "ui-tabs-selected ui-state-active" );
				$show.removeClass( "ui-tabs-hide" );
				self._trigger( "show", null, self._ui( clicked, $show[ 0 ] ) );
			};

		// Hide a tab, $show is optional...
		var hideTab = hideFx
			? function( clicked, $hide ) {
				$hide.animate( hideFx, hideFx.duration || "normal", function() {
					self.lis.removeClass( "ui-tabs-selected ui-state-active" );
					$hide.addClass( "ui-tabs-hide" );
					resetStyle( $hide, hideFx );
					self.element.dequeue( "tabs" );
				});
			}
			: function( clicked, $hide, $show ) {
				self.lis.removeClass( "ui-tabs-selected ui-state-active" );
				$hide.addClass( "ui-tabs-hide" );
				self.element.dequeue( "tabs" );
			};

		// attach tab event handler, unbind to avoid duplicates from former tabifying...
		this.anchors.bind( o.event + ".tabs", function() {
			var el = this,
				$li = $(el).closest( "li" ),
				$hide = self.panels.filter( ":not(.ui-tabs-hide)" ),
				$show = self.element.find( self._sanitizeSelector( el.hash ) );

			// If tab is already selected and not collapsible or tab disabled or
			// or is already loading or click callback returns false stop here.
			// Check if click handler returns false last so that it is not executed
			// for a disabled or loading tab!
			if ( ( $li.hasClass( "ui-tabs-selected" ) && !o.collapsible) ||
				$li.hasClass( "ui-state-disabled" ) ||
				$li.hasClass( "ui-state-processing" ) ||
				self.panels.filter( ":animated" ).length ||
				self._trigger( "select", null, self._ui( this, $show[ 0 ] ) ) === false ) {
				this.blur();
				return false;
			}

			o.selected = self.anchors.index( this );

			self.abort();

			// if tab may be closed
			if ( o.collapsible ) {
				if ( $li.hasClass( "ui-tabs-selected" ) ) {
					o.selected = -1;

					if ( o.cookie ) {
						self._cookie( o.selected, o.cookie );
					}

					self.element.queue( "tabs", function() {
						hideTab( el, $hide );
					}).dequeue( "tabs" );

					this.blur();
					return false;
				} else if ( !$hide.length ) {
					if ( o.cookie ) {
						self._cookie( o.selected, o.cookie );
					}

					self.element.queue( "tabs", function() {
						showTab( el, $show );
					});

					// TODO make passing in node possible, see also http://dev.jqueryui.com/ticket/3171
					self.load( self.anchors.index( this ) );

					this.blur();
					return false;
				}
			}

			if ( o.cookie ) {
				self._cookie( o.selected, o.cookie );
			}

			// show new tab
			if ( $show.length ) {
				if ( $hide.length ) {
					self.element.queue( "tabs", function() {
						hideTab( el, $hide );
					});
				}
				self.element.queue( "tabs", function() {
					showTab( el, $show );
				});

				self.load( self.anchors.index( this ) );
			} else {
				throw "jQuery UI Tabs: Mismatching fragment identifier.";
			}

			// Prevent IE from keeping other link focussed when using the back button
			// and remove dotted border from clicked link. This is controlled via CSS
			// in modern browsers; blur() removes focus from address bar in Firefox
			// which can become a usability and annoying problem with tabs('rotate').
			if ( $.browser.msie ) {
				this.blur();
			}
		});

		// disable click in any case
		this.anchors.bind( "click.tabs", function(){
			return false;
		});
	},

    _getIndex: function( index ) {
		// meta-function to give users option to provide a href string instead of a numerical index.
		// also sanitizes numerical indexes to valid values.
		if ( typeof index == "string" ) {
			index = this.anchors.index( this.anchors.filter( "[href$='" + index + "']" ) );
		}

		return index;
	},

	destroy: function() {
		var o = this.options;

		this.abort();

		this.element
			.unbind( ".tabs" )
			.removeClass( "ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible" )
			.removeData( "tabs" );

		this.list.removeClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" );

		this.anchors.each(function() {
			var href = $.data( this, "href.tabs" );
			if ( href ) {
				this.href = href;
			}
			var $this = $( this ).unbind( ".tabs" );
			$.each( [ "href", "load", "cache" ], function( i, prefix ) {
				$this.removeData( prefix + ".tabs" );
			});
		});

		this.lis.unbind( ".tabs" ).add( this.panels ).each(function() {
			if ( $.data( this, "destroy.tabs" ) ) {
				$( this ).remove();
			} else {
				$( this ).removeClass([
					"ui-state-default",
					"ui-corner-top",
					"ui-tabs-selected",
					"ui-state-active",
					"ui-state-hover",
					"ui-state-focus",
					"ui-state-disabled",
					"ui-tabs-panel",
					"ui-widget-content",
					"ui-corner-bottom",
					"ui-tabs-hide"
				].join( " " ) );
			}
		});

		if ( o.cookie ) {
			this._cookie( null, o.cookie );
		}

		return this;
	},

	add: function( url, label, index ) {
		if ( index === undefined ) {
			index = this.anchors.length;
		}

		var self = this,
			o = this.options,
			$li = $( o.tabTemplate.replace( /#\{href\}/g, url ).replace( /#\{label\}/g, label ) ),
			id = !url.indexOf( "#" ) ? url.replace( "#", "" ) : this._tabId( $( "a", $li )[ 0 ] );

		$li.addClass( "ui-state-default ui-corner-top" ).data( "destroy.tabs", true );

		// try to find an existing element before creating a new one
		var $panel = self.element.find( "#" + id );
		if ( !$panel.length ) {
			$panel = $( o.panelTemplate )
				.attr( "id", id )
				.data( "destroy.tabs", true );
		}
		$panel.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide" );

		if ( index >= this.lis.length ) {
			$li.appendTo( this.list );
			$panel.appendTo( this.list[ 0 ].parentNode );
		} else {
			$li.insertBefore( this.lis[ index ] );
			$panel.insertBefore( this.panels[ index ] );
		}

		o.disabled = $.map( o.disabled, function( n, i ) {
			return n >= index ? ++n : n;
		});

		this._tabify();

		if ( this.anchors.length == 1 ) {
			o.selected = 0;
			$li.addClass( "ui-tabs-selected ui-state-active" );
			$panel.removeClass( "ui-tabs-hide" );
			this.element.queue( "tabs", function() {
				self._trigger( "show", null, self._ui( self.anchors[ 0 ], self.panels[ 0 ] ) );
			});

			this.load( 0 );
		}

		this._trigger( "add", null, this._ui( this.anchors[ index ], this.panels[ index ] ) );
		return this;
	},

	remove: function( index ) {
		index = this._getIndex( index );
		var o = this.options,
			$li = this.lis.eq( index ).remove(),
			$panel = this.panels.eq( index ).remove();

		// If selected tab was removed focus tab to the right or
		// in case the last tab was removed the tab to the left.
		if ( $li.hasClass( "ui-tabs-selected" ) && this.anchors.length > 1) {
			this.select( index + ( index + 1 < this.anchors.length ? 1 : -1 ) );
		}

		o.disabled = $.map(
			$.grep( o.disabled, function(n, i) {
				return n != index;
			}),
			function( n, i ) {
				return n >= index ? --n : n;
			});

		this._tabify();

		this._trigger( "remove", null, this._ui( $li.find( "a" )[ 0 ], $panel[ 0 ] ) );
		return this;
	},

	enable: function( index ) {
		index = this._getIndex( index );
		var o = this.options;
		if ( $.inArray( index, o.disabled ) == -1 ) {
			return;
		}

		this.lis.eq( index ).removeClass( "ui-state-disabled" );
		o.disabled = $.grep( o.disabled, function( n, i ) {
			return n != index;
		});

		this._trigger( "enable", null, this._ui( this.anchors[ index ], this.panels[ index ] ) );
		return this;
	},

	disable: function( index ) {
		index = this._getIndex( index );
		var self = this, o = this.options;
		// cannot disable already selected tab
		if ( index != o.selected ) {
			this.lis.eq( index ).addClass( "ui-state-disabled" );

			o.disabled.push( index );
			o.disabled.sort();

			this._trigger( "disable", null, this._ui( this.anchors[ index ], this.panels[ index ] ) );
		}

		return this;
	},

	select: function( index ) {
		index = this._getIndex( index );
		if ( index == -1 ) {
			if ( this.options.collapsible && this.options.selected != -1 ) {
				index = this.options.selected;
			} else {
				return this;
			}
		}
		this.anchors.eq( index ).trigger( this.options.event + ".tabs" );
		return this;
	},

	load: function( index ) {
		index = this._getIndex( index );
		var self = this,
			o = this.options,
			a = this.anchors.eq( index )[ 0 ],
			url = $.data( a, "load.tabs" );

		this.abort();

		// not remote or from cache
		if ( !url || this.element.queue( "tabs" ).length !== 0 && $.data( a, "cache.tabs" ) ) {
			this.element.dequeue( "tabs" );
			return;
		}

		// load remote from here on
		this.lis.eq( index ).addClass( "ui-state-processing" );

		if ( o.spinner ) {
			var span = $( "span", a );
			span.data( "label.tabs", span.html() ).html( o.spinner );
		}

		this.xhr = $.ajax( $.extend( {}, o.ajaxOptions, {
			url: url,
			success: function( r, s ) {
				self.element.find( self._sanitizeSelector( a.hash ) ).html( r );

				// take care of tab labels
				self._cleanup();

				if ( o.cache ) {
					$.data( a, "cache.tabs", true );
				}

				self._trigger( "load", null, self._ui( self.anchors[ index ], self.panels[ index ] ) );
				try {
					o.ajaxOptions.success( r, s );
				}
				catch ( e ) {}
			},
			error: function( xhr, s, e ) {
				// take care of tab labels
				self._cleanup();

				self._trigger( "load", null, self._ui( self.anchors[ index ], self.panels[ index ] ) );
				try {
					// Passing index avoid a race condition when this method is
					// called after the user has selected another tab.
					// Pass the anchor that initiated this request allows
					// loadError to manipulate the tab content panel via $(a.hash)
					o.ajaxOptions.error( xhr, s, index, a );
				}
				catch ( e ) {}
			}
		} ) );

		// last, so that load event is fired before show...
		self.element.dequeue( "tabs" );

		return this;
	},

	abort: function() {
		// stop possibly running animations
		this.element.queue( [] );
		this.panels.stop( false, true );

		// "tabs" queue must not contain more than two elements,
		// which are the callbacks for the latest clicked tab...
		this.element.queue( "tabs", this.element.queue( "tabs" ).splice( -2, 2 ) );

		// terminate pending requests from other tabs
		if ( this.xhr ) {
			this.xhr.abort();
			delete this.xhr;
		}

		// take care of tab labels
		this._cleanup();
		return this;
	},

	url: function( index, url ) {
		this.anchors.eq( index ).removeData( "cache.tabs" ).data( "load.tabs", url );
		return this;
	},

	length: function() {
		return this.anchors.length;
	}
});

$.extend( $.ui.tabs, {
	version: "1.8.22"
});

/*
 * Tabs Extensions
 */

/*
 * Rotate
 */
$.extend( $.ui.tabs.prototype, {
	rotation: null,
	rotate: function( ms, continuing ) {
		var self = this,
			o = this.options;

		var rotate = self._rotate || ( self._rotate = function( e ) {
			clearTimeout( self.rotation );
			self.rotation = setTimeout(function() {
				var t = o.selected;
				self.select( ++t < self.anchors.length ? t : 0 );
			}, ms );
			
			if ( e ) {
				e.stopPropagation();
			}
		});

		var stop = self._unrotate || ( self._unrotate = !continuing
			? function(e) {
				if (e.clientX) { // in case of a true click
					self.rotate(null);
				}
			}
			: function( e ) {
				rotate();
			});

		// start rotation
		if ( ms ) {
			this.element.bind( "tabsshow", rotate );
			this.anchors.bind( o.event + ".tabs", stop );
			rotate();
		// stop rotation
		} else {
			clearTimeout( self.rotation );
			this.element.unbind( "tabsshow", rotate );
			this.anchors.unbind( o.event + ".tabs", stop );
			delete this._rotate;
			delete this._unrotate;
		}

		return this;
	}
});

})( jQuery );

console.log('=============== >  jquery-ui.js ');

/*
 * SimpleModal 1.4.1 - jQuery Plugin
 * http://www.ericmmartin.com/projects/simplemodal/
 * Copyright (c) 2010 Eric Martin (http://twitter.com/ericmmartin)
 * Dual licensed under the MIT and GPL licenses
 * Revision: $Id: jquery.simplemodal.js 127 2012-01-12 10:24:08Z laurent.mistahl $
 */
(function(d){var k=d.browser.msie&&parseInt(d.browser.version)===6&&typeof window.XMLHttpRequest!=="object",m=d.browser.msie&&parseInt(d.browser.version)===7,l=null,f=[];d.modal=function(a,b){return d.modal.impl.init(a,b)};d.modal.close=function(){d.modal.impl.close()};d.modal.focus=function(a){d.modal.impl.focus(a)};d.modal.setContainerDimensions=function(){d.modal.impl.setContainerDimensions()};d.modal.setPosition=function(){d.modal.impl.setPosition()};d.modal.update=function(a,b){d.modal.impl.update(a,
b)};d.fn.modal=function(a){return d.modal.impl.init(this,a)};d.modal.defaults={appendTo:"body",focus:true,opacity:50,overlayId:"simplemodal-overlay",overlayCss:{},containerId:"simplemodal-container",containerCss:{},dataId:"simplemodal-data",dataCss:{},minHeight:null,minWidth:null,maxHeight:null,maxWidth:null,autoResize:false,autoPosition:true,zIndex:1E3,close:true,closeHTML:'<a class="modalCloseImg" title="Close"></a>',closeClass:"simplemodal-close",escClose:true,overlayClose:false,position:null,
persist:false,modal:true,onOpen:null,onShow:null,onClose:null};d.modal.impl={d:{},init:function(a,b){var c=this;if(c.d.data)return false;l=d.browser.msie&&!d.boxModel;c.o=d.extend({},d.modal.defaults,b);c.zIndex=c.o.zIndex;c.occb=false;if(typeof a==="object"){a=a instanceof jQuery?a:d(a);c.d.placeholder=false;if(a.parent().parent().size()>0){a.before(d("<span></span>").attr("id","simplemodal-placeholder").css({display:"none"}));c.d.placeholder=true;c.display=a.css("display");if(!c.o.persist)c.d.orig=
a.clone(true)}}else if(typeof a==="string"||typeof a==="number")a=d("<div></div>").html(a);else{alert("SimpleModal Error: Unsupported data type: "+typeof a);return c}c.create(a);c.open();d.isFunction(c.o.onShow)&&c.o.onShow.apply(c,[c.d]);return c},create:function(a){var b=this;f=b.getDimensions();if(b.o.modal&&k)b.d.iframe=d('<iframe src="javascript:false;"></iframe>').css(d.extend(b.o.iframeCss,{display:"none",opacity:0,position:"fixed",height:f[0],width:f[1],zIndex:b.o.zIndex,top:0,left:0})).appendTo(b.o.appendTo);
b.d.overlay=d("<div></div>").attr("id",b.o.overlayId).addClass("simplemodal-overlay").css(d.extend(b.o.overlayCss,{display:"none",opacity:b.o.opacity/100,height:b.o.modal?f[0]:0,width:b.o.modal?f[1]:0,position:"fixed",left:0,top:0,zIndex:b.o.zIndex+1})).appendTo(b.o.appendTo);b.d.container=d("<div></div>").attr("id",b.o.containerId).addClass("simplemodal-container").css(d.extend(b.o.containerCss,{display:"none",position:"fixed",zIndex:b.o.zIndex+2})).append(b.o.close&&b.o.closeHTML?d(b.o.closeHTML).addClass(b.o.closeClass):
"").appendTo(b.o.appendTo);b.d.wrap=d("<div></div>").attr("tabIndex",-1).addClass("simplemodal-wrap").css({height:"100%",outline:0,width:"100%"}).appendTo(b.d.container);b.d.data=a.attr("id",a.attr("id")||b.o.dataId).addClass("simplemodal-data").css(d.extend(b.o.dataCss,{display:"none"})).appendTo("body");b.setContainerDimensions();b.d.data.appendTo(b.d.wrap);if(k||l)b.fixIE()},bindEvents:function(){var a=this;d("."+a.o.closeClass).bind("click.simplemodal",function(b){b.preventDefault();a.close()});
a.o.modal&&a.o.close&&a.o.overlayClose&&a.d.overlay.bind("click.simplemodal",function(b){b.preventDefault();a.close()});d(document).bind("keydown.simplemodal",function(b){if(a.o.modal&&b.keyCode===9)a.watchTab(b);else if(a.o.close&&a.o.escClose&&b.keyCode===27){b.preventDefault();a.close()}});d(window).bind("resize.simplemodal",function(){f=a.getDimensions();a.o.autoResize?a.setContainerDimensions():a.o.autoPosition&&a.setPosition();if(k||l)a.fixIE();else if(a.o.modal){a.d.iframe&&a.d.iframe.css({height:f[0],
width:f[1]});a.d.overlay.css({height:f[0],width:f[1]})}})},unbindEvents:function(){d("."+this.o.closeClass).unbind("click.simplemodal");d(document).unbind("keydown.simplemodal");d(window).unbind("resize.simplemodal");this.d.overlay.unbind("click.simplemodal")},fixIE:function(){var a=this,b=a.o.position;d.each([a.d.iframe||null,!a.o.modal?null:a.d.overlay,a.d.container],function(c,h){if(h){var g=h[0].style;g.position="absolute";if(c<2){g.removeExpression("height");g.removeExpression("width");g.setExpression("height",
'document.body.scrollHeight > document.body.clientHeight ? document.body.scrollHeight : document.body.clientHeight + "px"');g.setExpression("width",'document.body.scrollWidth > document.body.clientWidth ? document.body.scrollWidth : document.body.clientWidth + "px"')}else{var e;if(b&&b.constructor===Array){c=b[0]?typeof b[0]==="number"?b[0].toString():b[0].replace(/px/,""):h.css("top").replace(/px/,"");c=c.indexOf("%")===-1?c+' + (t = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"':
parseInt(c.replace(/%/,""))+' * ((document.documentElement.clientHeight || document.body.clientHeight) / 100) + (t = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"';if(b[1]){e=typeof b[1]==="number"?b[1].toString():b[1].replace(/px/,"");e=e.indexOf("%")===-1?e+' + (t = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft) + "px"':parseInt(e.replace(/%/,""))+' * ((document.documentElement.clientWidth || document.body.clientWidth) / 100) + (t = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft) + "px"'}}else{c=
'(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (t = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"';e='(document.documentElement.clientWidth || document.body.clientWidth) / 2 - (this.offsetWidth / 2) + (t = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft) + "px"'}g.removeExpression("top");g.removeExpression("left");g.setExpression("top",
c);g.setExpression("left",e)}}})},focus:function(a){var b=this;a=a&&d.inArray(a,["first","last"])!==-1?a:"first";var c=d(":input:enabled:visible:"+a,b.d.wrap);setTimeout(function(){c.length>0?c.focus():b.d.wrap.focus()},10)},getDimensions:function(){var a=d(window);return[d.browser.opera&&d.browser.version>"9.5"&&d.fn.jquery<"1.3"||d.browser.opera&&d.browser.version<"9.5"&&d.fn.jquery>"1.2.6"?a[0].innerHeight:a.height(),a.width()]},getVal:function(a,b){return a?typeof a==="number"?a:a==="auto"?0:
a.indexOf("%")>0?parseInt(a.replace(/%/,""))/100*(b==="h"?f[0]:f[1]):parseInt(a.replace(/px/,"")):null},update:function(a,b){var c=this;if(!c.d.data)return false;c.d.origHeight=c.getVal(a,"h");c.d.origWidth=c.getVal(b,"w");c.d.data.hide();a&&c.d.container.css("height",a);b&&c.d.container.css("width",b);c.setContainerDimensions();c.d.data.show();c.o.focus&&c.focus();c.unbindEvents();c.bindEvents()},setContainerDimensions:function(){var a=this,b=k||m,c=a.d.origHeight?a.d.origHeight:d.browser.opera?
a.d.container.height():a.getVal(b?a.d.container[0].currentStyle.height:a.d.container.css("height"),"h");b=a.d.origWidth?a.d.origWidth:d.browser.opera?a.d.container.width():a.getVal(b?a.d.container[0].currentStyle.width:a.d.container.css("width"),"w");var h=a.d.data.outerHeight(true),g=a.d.data.outerWidth(true);a.d.origHeight=a.d.origHeight||c;a.d.origWidth=a.d.origWidth||b;var e=a.o.maxHeight?a.getVal(a.o.maxHeight,"h"):null,i=a.o.maxWidth?a.getVal(a.o.maxWidth,"w"):null;e=e&&e<f[0]?e:f[0];i=i&&i<
f[1]?i:f[1];var j=a.o.minHeight?a.getVal(a.o.minHeight,"h"):"auto";c=c?a.o.autoResize&&c>e?e:c<j?j:c:h?h>e?e:a.o.minHeight&&j!=="auto"&&h<j?j:h:j;e=a.o.minWidth?a.getVal(a.o.minWidth,"w"):"auto";b=b?a.o.autoResize&&b>i?i:b<e?e:b:g?g>i?i:a.o.minWidth&&e!=="auto"&&g<e?e:g:e;a.d.container.css({height:c,width:b});a.d.wrap.css({overflow:h>c||g>b?"auto":"visible"});a.o.autoPosition&&a.setPosition()},setPosition:function(){var a=this,b,c;b=f[0]/2-a.d.container.outerHeight(true)/2;c=f[1]/2-a.d.container.outerWidth(true)/
2;if(a.o.position&&Object.prototype.toString.call(a.o.position)==="[object Array]"){b=a.o.position[0]||b;c=a.o.position[1]||c}else{b=b;c=c}a.d.container.css({left:c,top:b})},watchTab:function(a){var b=this;if(d(a.target).parents(".simplemodal-container").length>0){b.inputs=d(":input:enabled:visible:first, :input:enabled:visible:last",b.d.data[0]);if(!a.shiftKey&&a.target===b.inputs[b.inputs.length-1]||a.shiftKey&&a.target===b.inputs[0]||b.inputs.length===0){a.preventDefault();b.focus(a.shiftKey?"last":
"first")}}else{a.preventDefault();b.focus()}},open:function(){var a=this;a.d.iframe&&a.d.iframe.show();if(d.isFunction(a.o.onOpen))a.o.onOpen.apply(a,[a.d]);else{a.d.overlay.show();a.d.container.show();a.d.data.show()}a.o.focus&&a.focus();a.bindEvents()},close:function(){var a=this;if(!a.d.data)return false;a.unbindEvents();if(d.isFunction(a.o.onClose)&&!a.occb){a.occb=true;a.o.onClose.apply(a,[a.d])}else{if(a.d.placeholder){var b=d("#simplemodal-placeholder");if(a.o.persist)b.replaceWith(a.d.data.removeClass("simplemodal-data").css("display",
a.display));else{a.d.data.hide().remove();b.replaceWith(a.d.orig)}}else a.d.data.hide().remove();a.d.container.hide().remove();a.d.overlay.hide();a.d.iframe&&a.d.iframe.hide().remove();setTimeout(function(){if (a.d.overlay)a.d.overlay.remove();a.d={}},10)}}}})(jQuery);

console.log('=============== >  jquery.simplemodal.js ');

// jQuery Alert Dialogs Plugin
//
// Version 1.1
//
// Cory S.N. LaViska
// A Beautiful Site (http://abeautifulsite.net/)
// 14 May 2009
//
// Visit http://abeautifulsite.net/notebook/87 for more information
//
// Usage:
//		jAlert( message, [title, callback] )
//		jConfirm( message, [title, callback] )
//		jPrompt( message, [value, title, callback] )
// 
// History:
//
//		1.00 - Released (29 December 2008)
//
//		1.01 - Fixed bug where unbinding would destroy all resize events
//
// License:
// 
// This plugin is dual-licensed under the GNU General Public License and the MIT License and
// is copyright 2008 A Beautiful Site, LLC. 
//
(function($) {
	
	$.alerts = {
		
		// These properties can be read/written by accessing $.alerts.propertyName from your scripts at any time
		
		verticalOffset: -75,                // vertical offset of the dialog from center screen, in pixels
		horizontalOffset: 0,                // horizontal offset of the dialog from center screen, in pixels/
		repositionOnResize: true,           // re-centers the dialog on window resize
		overlayOpacity: .01,                // transparency level of overlay
		overlayColor: '#FFF',               // base color of overlay
		draggable: true,                    // make the dialogs draggable (requires UI Draggables plugin)
		okButton: '&nbsp;OK&nbsp;',         // text for the OK button
		cancelButton: '&nbsp;Cancel&nbsp;', // text for the Cancel button
		dialogClass: null,                  // if specified, this class will be applied to all dialogs
		
		// Public methods
		
		alert: function(message, title, callback) {
			if( title == null ) title = 'Alert';
			$.alerts._show(title, message, null, 'alert', function(result) {
				if( callback ) callback(result);
			});
		},
		
		confirm: function(message, title, callback) {
			if( title == null ) title = 'Confirm';
			$.alerts._show(title, message, null, 'confirm', function(result) {
				if( callback ) callback(result);
			});
		},
			
		prompt: function(message, value, title, callback) {
			if( title == null ) title = 'Prompt';
			$.alerts._show(title, message, value, 'prompt', function(result) {
				if( callback ) callback(result);
			});
		},
		
		// Private methods
		
		_show: function(title, msg, value, type, callback) {
			
			$.alerts._hide();
			$.alerts._overlay('show');
			
			$("BODY").append(
			  '<div id="popup_container">' +
			    '<h1 id="popup_title"></h1>' +
			    '<div id="popup_content">' +
			      '<div id="popup_message"></div>' +
				'</div>' +
			  '</div>');
			
			if( $.alerts.dialogClass ) $("#popup_container").addClass($.alerts.dialogClass);
			
			// IE6 Fix
			var pos = ($.browser.msie && parseInt($.browser.version) <= 6 ) ? 'absolute' : 'fixed'; 
			
			$("#popup_container").css({
				position: pos,
				zIndex: 99999,
				padding: 0,
				margin: 0
			});
			
			$("#popup_title").text(title);
			$("#popup_content").addClass(type);
			$("#popup_message").text(msg);
			$("#popup_message").html( $("#popup_message").text().replace(/\n/g, '<br />') );
			
			$("#popup_container").css({
				minWidth: $("#popup_container").outerWidth(),
				maxWidth: $("#popup_container").outerWidth()
			});
			
			$.alerts._reposition();
			$.alerts._maintainPosition(true);
			
			switch( type ) {
				case 'alert':
					$("#popup_message").after('<div id="popup_panel"><input type="button" value="' + $.alerts.okButton + '" id="popup_ok" /></div>');
					$("#popup_ok").click( function() {
						$.alerts._hide();
						callback(true);
					});
					$("#popup_ok").focus().keypress( function(e) {
						if( e.keyCode == 13 || e.keyCode == 27 ) $("#popup_ok").trigger('click');
					});
				break;
				case 'confirm':
					$("#popup_message").after('<div id="popup_panel"><input type="button" value="' + $.alerts.okButton + '" id="popup_ok" /> <input type="button" value="' + $.alerts.cancelButton + '" id="popup_cancel" /></div>');
					$("#popup_ok").click( function() {
						$.alerts._hide();
						if( callback ) callback(true);
					});
					$("#popup_cancel").click( function() {
						$.alerts._hide();
						if( callback ) callback(false);
					});
					$("#popup_ok").focus();
					$("#popup_ok, #popup_cancel").keypress( function(e) {
						if( e.keyCode == 13 ) $("#popup_ok").trigger('click');
						if( e.keyCode == 27 ) $("#popup_cancel").trigger('click');
					});
				break;
				case 'prompt':
					$("#popup_message").append('<br /><input type="text" size="30" id="popup_prompt" />').after('<div id="popup_panel"><input type="button" value="' + $.alerts.okButton + '" id="popup_ok" /> <input type="button" value="' + $.alerts.cancelButton + '" id="popup_cancel" /></div>');
					$("#popup_prompt").width( $("#popup_message").width() );
					$("#popup_ok").click( function() {
						var val = $("#popup_prompt").val();
						$.alerts._hide();
						if( callback ) callback( val );
					});
					$("#popup_cancel").click( function() {
						$.alerts._hide();
						if( callback ) callback( null );
					});
					$("#popup_prompt, #popup_ok, #popup_cancel").keypress( function(e) {
						if( e.keyCode == 13 ) $("#popup_ok").trigger('click');
						if( e.keyCode == 27 ) $("#popup_cancel").trigger('click');
					});
					if( value ) $("#popup_prompt").val(value);
					$("#popup_prompt").focus().select();
				break;
			}
			
			// Make draggable
			if( $.alerts.draggable ) {
				try {
					$("#popup_container").draggable({ handle: $("#popup_title") });
					$("#popup_title").css({ cursor: 'move' });
				} catch(e) { /* requires jQuery UI draggables */ }
			}
		},
		
		_hide: function() {
			$("#popup_container").remove();
			$.alerts._overlay('hide');
			$.alerts._maintainPosition(false);
		},
		
		_overlay: function(status) {
			switch( status ) {
				case 'show':
					$.alerts._overlay('hide');
					$("BODY").append('<div id="popup_overlay"></div>');
					$("#popup_overlay").css({
						position: 'absolute',
						zIndex: 99998,
						top: '0px',
						left: '0px',
						width: '100%',
						height: $(document).height(),
						background: $.alerts.overlayColor,
						opacity: $.alerts.overlayOpacity
					});
				break;
				case 'hide':
					$("#popup_overlay").remove();
				break;
			}
		},
		
		_reposition: function() {
			var top = (($(window).height() / 2) - ($("#popup_container").outerHeight() / 2)) + $.alerts.verticalOffset;
			var left = (($(window).width() / 2) - ($("#popup_container").outerWidth() / 2)) + $.alerts.horizontalOffset;
			if( top < 0 ) top = 0;
			if( left < 0 ) left = 0;
			
			// IE6 fix
			if( $.browser.msie && parseInt($.browser.version) <= 6 ) top = top + $(window).scrollTop();
			
			$("#popup_container").css({
				top: top + 'px',
				left: left + 'px'
			});
			$("#popup_overlay").height( $(document).height() );
		},
		
		_maintainPosition: function(status) {
			if( $.alerts.repositionOnResize ) {
				switch(status) {
					case true:
						$(window).bind('resize', $.alerts._reposition);
					break;
					case false:
						$(window).unbind('resize', $.alerts._reposition);
					break;
				}
			}
		}
		
	}
	
	// Shortuct functions
	jAlert = function(message, title, callback) {
		$.alerts.alert(message, title, callback);
	}
	
	jConfirm = function(message, title, callback) {
		$.alerts.confirm(message, title, callback);
	};
		
	jPrompt = function(message, value, title, callback) {
		$.alerts.prompt(message, value, title, callback);
	};
	
})(jQuery);
console.log('=============== >  jquery.alerts.js ');

/**
 * @summary     DataTables
 * @description Paginate, search and sort HTML tables
 * @version     1.9.4
 * @file        jquery.dataTables.js
 * @author      Allan Jardine (www.sprymedia.co.uk)
 * @contact     www.sprymedia.co.uk/contact
 *
 * @copyright Copyright 2008-2012 Allan Jardine, all rights reserved.
 *
 * This source file is free software, under either the GPL v2 license or a
 * BSD style license, available at:
 *   http://datatables.net/license_gpl2
 *   http://datatables.net/license_bsd
 * 
 * This source file is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
 * 
 * For details please refer to: http://www.datatables.net
 */

/*jslint evil: true, undef: true, browser: true */
/*globals $, jQuery,define,_fnExternApiFunc,_fnInitialise,_fnInitComplete,_fnLanguageCompat,_fnAddColumn,_fnColumnOptions,_fnAddData,_fnCreateTr,_fnGatherData,_fnBuildHead,_fnDrawHead,_fnDraw,_fnReDraw,_fnAjaxUpdate,_fnAjaxParameters,_fnAjaxUpdateDraw,_fnServerParams,_fnAddOptionsHtml,_fnFeatureHtmlTable,_fnScrollDraw,_fnAdjustColumnSizing,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnBuildSearchArray,_fnBuildSearchRow,_fnFilterCreateSearch,_fnDataToSearch,_fnSort,_fnSortAttachListener,_fnSortingClasses,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnFeatureHtmlLength,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnNodeToDataIndex,_fnVisbleColumns,_fnCalculateEnd,_fnConvertToWidth,_fnCalculateColumnWidths,_fnScrollingWidthAdjust,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnDetectType,_fnSettingsFromNode,_fnGetDataMaster,_fnGetTrNodes,_fnGetTdNodes,_fnEscapeRegex,_fnDeleteIndex,_fnReOrderIndex,_fnColumnOrdering,_fnLog,_fnClearTable,_fnSaveState,_fnLoadState,_fnCreateCookie,_fnReadCookie,_fnDetectHeader,_fnGetUniqueThs,_fnScrollBarWidth,_fnApplyToChildren,_fnMap,_fnGetRowData,_fnGetCellData,_fnSetCellData,_fnGetObjectDataFn,_fnSetObjectDataFn,_fnApplyColumnDefs,_fnBindAction,_fnCallbackReg,_fnCallbackFire,_fnJsonString,_fnRender,_fnNodeToColumnIndex,_fnInfoMacros,_fnBrowserDetect,_fnGetColumns*/

(/** @lends <global> */function( window, document, undefined ) {

(function( factory ) {
	"use strict";

	// Define as an AMD module if possible
	if ( typeof define === 'function' && define.amd )
	{
		define( ['jquery'], factory );
	}
	/* Define using browser globals otherwise
	 * Prevent multiple instantiations if the script is loaded twice
	 */
	else if ( jQuery && !jQuery.fn.dataTable )
	{
		factory( jQuery );
	}
}
(/** @lends <global> */function( $ ) {
	"use strict";
	/** 
	 * DataTables is a plug-in for the jQuery Javascript library. It is a 
	 * highly flexible tool, based upon the foundations of progressive 
	 * enhancement, which will add advanced interaction controls to any 
	 * HTML table. For a full list of features please refer to
	 * <a href="http://datatables.net">DataTables.net</a>.
	 *
	 * Note that the <i>DataTable</i> object is not a global variable but is
	 * aliased to <i>jQuery.fn.DataTable</i> and <i>jQuery.fn.dataTable</i> through which 
	 * it may be  accessed.
	 *
	 *  @class
	 *  @param {object} [oInit={}] Configuration object for DataTables. Options
	 *    are defined by {@link DataTable.defaults}
	 *  @requires jQuery 1.3+
	 * 
	 *  @example
	 *    // Basic initialisation
	 *    $(document).ready( function {
	 *      $('#example').dataTable();
	 *    } );
	 *  
	 *  @example
	 *    // Initialisation with configuration options - in this case, disable
	 *    // pagination and sorting.
	 *    $(document).ready( function {
	 *      $('#example').dataTable( {
	 *        "bPaginate": false,
	 *        "bSort": false 
	 *      } );
	 *    } );
	 */
	var DataTable = function( oInit )
	{
		
		
		/**
		 * Add a column to the list used for the table with default values
		 *  @param {object} oSettings dataTables settings object
		 *  @param {node} nTh The th element for this column
		 *  @memberof DataTable#oApi
		 */
		function _fnAddColumn( oSettings, nTh )
		{
			var oDefaults = DataTable.defaults.columns;
			var iCol = oSettings.aoColumns.length;
			var oCol = $.extend( {}, DataTable.models.oColumn, oDefaults, {
				"sSortingClass": oSettings.oClasses.sSortable,
				"sSortingClassJUI": oSettings.oClasses.sSortJUI,
				"nTh": nTh ? nTh : document.createElement('th'),
				"sTitle":    oDefaults.sTitle    ? oDefaults.sTitle    : nTh ? nTh.innerHTML : '',
				"aDataSort": oDefaults.aDataSort ? oDefaults.aDataSort : [iCol],
				"mData": oDefaults.mData ? oDefaults.oDefaults : iCol
			} );
			oSettings.aoColumns.push( oCol );
			
			/* Add a column specific filter */
			if ( oSettings.aoPreSearchCols[ iCol ] === undefined || oSettings.aoPreSearchCols[ iCol ] === null )
			{
				oSettings.aoPreSearchCols[ iCol ] = $.extend( {}, DataTable.models.oSearch );
			}
			else
			{
				var oPre = oSettings.aoPreSearchCols[ iCol ];
				
				/* Don't require that the user must specify bRegex, bSmart or bCaseInsensitive */
				if ( oPre.bRegex === undefined )
				{
					oPre.bRegex = true;
				}
				
				if ( oPre.bSmart === undefined )
				{
					oPre.bSmart = true;
				}
				
				if ( oPre.bCaseInsensitive === undefined )
				{
					oPre.bCaseInsensitive = true;
				}
			}
			
			/* Use the column options function to initialise classes etc */
			_fnColumnOptions( oSettings, iCol, null );
		}
		
		
		/**
		 * Apply options for a column
		 *  @param {object} oSettings dataTables settings object
		 *  @param {int} iCol column index to consider
		 *  @param {object} oOptions object with sType, bVisible and bSearchable etc
		 *  @memberof DataTable#oApi
		 */
		function _fnColumnOptions( oSettings, iCol, oOptions )
		{
			var oCol = oSettings.aoColumns[ iCol ];
			
			/* User specified column options */
			if ( oOptions !== undefined && oOptions !== null )
			{
				/* Backwards compatibility for mDataProp */
				if ( oOptions.mDataProp && !oOptions.mData )
				{
					oOptions.mData = oOptions.mDataProp;
				}
		
				if ( oOptions.sType !== undefined )
				{
					oCol.sType = oOptions.sType;
					oCol._bAutoType = false;
				}
				
				$.extend( oCol, oOptions );
				_fnMap( oCol, oOptions, "sWidth", "sWidthOrig" );
		
				/* iDataSort to be applied (backwards compatibility), but aDataSort will take
				 * priority if defined
				 */
				if ( oOptions.iDataSort !== undefined )
				{
					oCol.aDataSort = [ oOptions.iDataSort ];
				}
				_fnMap( oCol, oOptions, "aDataSort" );
			}
		
			/* Cache the data get and set functions for speed */
			var mRender = oCol.mRender ? _fnGetObjectDataFn( oCol.mRender ) : null;
			var mData = _fnGetObjectDataFn( oCol.mData );
		
			oCol.fnGetData = function (oData, sSpecific) {
				var innerData = mData( oData, sSpecific );
		
				if ( oCol.mRender && (sSpecific && sSpecific !== '') )
				{
					return mRender( innerData, sSpecific, oData );
				}
				return innerData;
			};
			oCol.fnSetData = _fnSetObjectDataFn( oCol.mData );
			
			/* Feature sorting overrides column specific when off */
			if ( !oSettings.oFeatures.bSort )
			{
				oCol.bSortable = false;
			}
			
			/* Check that the class assignment is correct for sorting */
			if ( !oCol.bSortable ||
				 ($.inArray('asc', oCol.asSorting) == -1 && $.inArray('desc', oCol.asSorting) == -1) )
			{
				oCol.sSortingClass = oSettings.oClasses.sSortableNone;
				oCol.sSortingClassJUI = "";
			}
			else if ( $.inArray('asc', oCol.asSorting) == -1 && $.inArray('desc', oCol.asSorting) == -1 )
			{
				oCol.sSortingClass = oSettings.oClasses.sSortable;
				oCol.sSortingClassJUI = oSettings.oClasses.sSortJUI;
			}
			else if ( $.inArray('asc', oCol.asSorting) != -1 && $.inArray('desc', oCol.asSorting) == -1 )
			{
				oCol.sSortingClass = oSettings.oClasses.sSortableAsc;
				oCol.sSortingClassJUI = oSettings.oClasses.sSortJUIAscAllowed;
			}
			else if ( $.inArray('asc', oCol.asSorting) == -1 && $.inArray('desc', oCol.asSorting) != -1 )
			{
				oCol.sSortingClass = oSettings.oClasses.sSortableDesc;
				oCol.sSortingClassJUI = oSettings.oClasses.sSortJUIDescAllowed;
			}
		}
		
		
		/**
		 * Adjust the table column widths for new data. Note: you would probably want to 
		 * do a redraw after calling this function!
		 *  @param {object} oSettings dataTables settings object
		 *  @memberof DataTable#oApi
		 */
		function _fnAdjustColumnSizing ( oSettings )
		{
			/* Not interested in doing column width calculation if auto-width is disabled */
			if ( oSettings.oFeatures.bAutoWidth === false )
			{
				return false;
			}
			
			_fnCalculateColumnWidths( oSettings );
			for ( var i=0 , iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
			{
				oSettings.aoColumns[i].nTh.style.width = oSettings.aoColumns[i].sWidth;
			}
		}
		
		
		/**
		 * Covert the index of a visible column to the index in the data array (take account
		 * of hidden columns)
		 *  @param {object} oSettings dataTables settings object
		 *  @param {int} iMatch Visible column index to lookup
		 *  @returns {int} i the data index
		 *  @memberof DataTable#oApi
		 */
		function _fnVisibleToColumnIndex( oSettings, iMatch )
		{
			var aiVis = _fnGetColumns( oSettings, 'bVisible' );
		
			return typeof aiVis[iMatch] === 'number' ?
				aiVis[iMatch] :
				null;
		}
		
		
		/**
		 * Covert the index of an index in the data array and convert it to the visible
		 *   column index (take account of hidden columns)
		 *  @param {int} iMatch Column index to lookup
		 *  @param {object} oSettings dataTables settings object
		 *  @returns {int} i the data index
		 *  @memberof DataTable#oApi
		 */
		function _fnColumnIndexToVisible( oSettings, iMatch )
		{
			var aiVis = _fnGetColumns( oSettings, 'bVisible' );
			var iPos = $.inArray( iMatch, aiVis );
		
			return iPos !== -1 ? iPos : null;
		}
		
		
		/**
		 * Get the number of visible columns
		 *  @param {object} oSettings dataTables settings object
		 *  @returns {int} i the number of visible columns
		 *  @memberof DataTable#oApi
		 */
		function _fnVisbleColumns( oSettings )
		{
			return _fnGetColumns( oSettings, 'bVisible' ).length;
		}
		
		
		/**
		 * Get an array of column indexes that match a given property
		 *  @param {object} oSettings dataTables settings object
		 *  @param {string} sParam Parameter in aoColumns to look for - typically 
		 *    bVisible or bSearchable
		 *  @returns {array} Array of indexes with matched properties
		 *  @memberof DataTable#oApi
		 */
		function _fnGetColumns( oSettings, sParam )
		{
			var a = [];
		
			$.map( oSettings.aoColumns, function(val, i) {
				if ( val[sParam] ) {
					a.push( i );
				}
			} );
		
			return a;
		}
		
		
		/**
		 * Get the sort type based on an input string
		 *  @param {string} sData data we wish to know the type of
		 *  @returns {string} type (defaults to 'string' if no type can be detected)
		 *  @memberof DataTable#oApi
		 */
		function _fnDetectType( sData )
		{
			var aTypes = DataTable.ext.aTypes;
			var iLen = aTypes.length;
			
			for ( var i=0 ; i<iLen ; i++ )
			{
				var sType = aTypes[i]( sData );
				if ( sType !== null )
				{
					return sType;
				}
			}
			
			return 'string';
		}
		
		
		/**
		 * Figure out how to reorder a display list
		 *  @param {object} oSettings dataTables settings object
		 *  @returns array {int} aiReturn index list for reordering
		 *  @memberof DataTable#oApi
		 */
		function _fnReOrderIndex ( oSettings, sColumns )
		{
			var aColumns = sColumns.split(',');
			var aiReturn = [];
			
			for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
			{
				for ( var j=0 ; j<iLen ; j++ )
				{
					if ( oSettings.aoColumns[i].sName == aColumns[j] )
					{
						aiReturn.push( j );
						break;
					}
				}
			}
			
			return aiReturn;
		}
		
		
		/**
		 * Get the column ordering that DataTables expects
		 *  @param {object} oSettings dataTables settings object
		 *  @returns {string} comma separated list of names
		 *  @memberof DataTable#oApi
		 */
		function _fnColumnOrdering ( oSettings )
		{
			var sNames = '';
			for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
			{
				sNames += oSettings.aoColumns[i].sName+',';
			}
			if ( sNames.length == iLen )
			{
				return "";
			}
			return sNames.slice(0, -1);
		}
		
		
		/**
		 * Take the column definitions and static columns arrays and calculate how
		 * they relate to column indexes. The callback function will then apply the
		 * definition found for a column to a suitable configuration object.
		 *  @param {object} oSettings dataTables settings object
		 *  @param {array} aoColDefs The aoColumnDefs array that is to be applied
		 *  @param {array} aoCols The aoColumns array that defines columns individually
		 *  @param {function} fn Callback function - takes two parameters, the calculated
		 *    column index and the definition for that column.
		 *  @memberof DataTable#oApi
		 */
		function _fnApplyColumnDefs( oSettings, aoColDefs, aoCols, fn )
		{
			var i, iLen, j, jLen, k, kLen;
		
			// Column definitions with aTargets
			if ( aoColDefs )
			{
				/* Loop over the definitions array - loop in reverse so first instance has priority */
				for ( i=aoColDefs.length-1 ; i>=0 ; i-- )
				{
					/* Each definition can target multiple columns, as it is an array */
					var aTargets = aoColDefs[i].aTargets;
					if ( !$.isArray( aTargets ) )
					{
						_fnLog( oSettings, 1, 'aTargets must be an array of targets, not a '+(typeof aTargets) );
					}
		
					for ( j=0, jLen=aTargets.length ; j<jLen ; j++ )
					{
						if ( typeof aTargets[j] === 'number' && aTargets[j] >= 0 )
						{
							/* Add columns that we don't yet know about */
							while( oSettings.aoColumns.length <= aTargets[j] )
							{
								_fnAddColumn( oSettings );
							}
		
							/* Integer, basic index */
							fn( aTargets[j], aoColDefs[i] );
						}
						else if ( typeof aTargets[j] === 'number' && aTargets[j] < 0 )
						{
							/* Negative integer, right to left column counting */
							fn( oSettings.aoColumns.length+aTargets[j], aoColDefs[i] );
						}
						else if ( typeof aTargets[j] === 'string' )
						{
							/* Class name matching on TH element */
							for ( k=0, kLen=oSettings.aoColumns.length ; k<kLen ; k++ )
							{
								if ( aTargets[j] == "_all" ||
								     $(oSettings.aoColumns[k].nTh).hasClass( aTargets[j] ) )
								{
									fn( k, aoColDefs[i] );
								}
							}
						}
					}
				}
			}
		
			// Statically defined columns array
			if ( aoCols )
			{
				for ( i=0, iLen=aoCols.length ; i<iLen ; i++ )
				{
					fn( i, aoCols[i] );
				}
			}
		}
		
		/**
		 * Add a data array to the table, creating DOM node etc. This is the parallel to 
		 * _fnGatherData, but for adding rows from a Javascript source, rather than a
		 * DOM source.
		 *  @param {object} oSettings dataTables settings object
		 *  @param {array} aData data array to be added
		 *  @returns {int} >=0 if successful (index of new aoData entry), -1 if failed
		 *  @memberof DataTable#oApi
		 */
		function _fnAddData ( oSettings, aDataSupplied )
		{
			var oCol;
			
			/* Take an independent copy of the data source so we can bash it about as we wish */
			var aDataIn = ($.isArray(aDataSupplied)) ?
				aDataSupplied.slice() :
				$.extend( true, {}, aDataSupplied );
			
			/* Create the object for storing information about this new row */
			var iRow = oSettings.aoData.length;
			var oData = $.extend( true, {}, DataTable.models.oRow );
			oData._aData = aDataIn;
			oSettings.aoData.push( oData );
		
			/* Create the cells */
			var nTd, sThisType;
			for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
			{
				oCol = oSettings.aoColumns[i];
		
				/* Use rendered data for filtering / sorting */
				if ( typeof oCol.fnRender === 'function' && oCol.bUseRendered && oCol.mData !== null )
				{
					_fnSetCellData( oSettings, iRow, i, _fnRender(oSettings, iRow, i) );
				}
				else
				{
					_fnSetCellData( oSettings, iRow, i, _fnGetCellData( oSettings, iRow, i ) );
				}
				
				/* See if we should auto-detect the column type */
				if ( oCol._bAutoType && oCol.sType != 'string' )
				{
					/* Attempt to auto detect the type - same as _fnGatherData() */
					var sVarType = _fnGetCellData( oSettings, iRow, i, 'type' );
					if ( sVarType !== null && sVarType !== '' )
					{
						sThisType = _fnDetectType( sVarType );
						if ( oCol.sType === null )
						{
							oCol.sType = sThisType;
						}
						else if ( oCol.sType != sThisType && oCol.sType != "html" )
						{
							/* String is always the 'fallback' option */
							oCol.sType = 'string';
						}
					}
				}
			}
			
			/* Add to the display array */
			oSettings.aiDisplayMaster.push( iRow );
		
			/* Create the DOM information */
			if ( !oSettings.oFeatures.bDeferRender )
			{
				_fnCreateTr( oSettings, iRow );
			}
		
			return iRow;
		}
		
		
		/**
		 * Read in the data from the target table from the DOM
		 *  @param {object} oSettings dataTables settings object
		 *  @memberof DataTable#oApi
		 */
		function _fnGatherData( oSettings )
		{
			var iLoop, i, iLen, j, jLen, jInner,
			 	nTds, nTrs, nTd, nTr, aLocalData, iThisIndex,
				iRow, iRows, iColumn, iColumns, sNodeName,
				oCol, oData;
			
			/*
			 * Process by row first
			 * Add the data object for the whole table - storing the tr node. Note - no point in getting
			 * DOM based data if we are going to go and replace it with Ajax source data.
			 */
			if ( oSettings.bDeferLoading || oSettings.sAjaxSource === null )
			{
				nTr = oSettings.nTBody.firstChild;
				while ( nTr )
				{
					if ( nTr.nodeName.toUpperCase() == "TR" )
					{
						iThisIndex = oSettings.aoData.length;
						nTr._DT_RowIndex = iThisIndex;
						oSettings.aoData.push( $.extend( true, {}, DataTable.models.oRow, {
							"nTr": nTr
						} ) );
		
						oSettings.aiDisplayMaster.push( iThisIndex );
						nTd = nTr.firstChild;
						jInner = 0;
						while ( nTd )
						{
							sNodeName = nTd.nodeName.toUpperCase();
							if ( sNodeName == "TD" || sNodeName == "TH" )
							{
								_fnSetCellData( oSettings, iThisIndex, jInner, $.trim(nTd.innerHTML) );
								jInner++;
							}
							nTd = nTd.nextSibling;
						}
					}
					nTr = nTr.nextSibling;
				}
			}
			
			/* Gather in the TD elements of the Table - note that this is basically the same as
			 * fnGetTdNodes, but that function takes account of hidden columns, which we haven't yet
			 * setup!
			 */
			nTrs = _fnGetTrNodes( oSettings );
			nTds = [];
			for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
			{
				nTd = nTrs[i].firstChild;
				while ( nTd )
				{
					sNodeName = nTd.nodeName.toUpperCase();
					if ( sNodeName == "TD" || sNodeName == "TH" )
					{
						nTds.push( nTd );
					}
					nTd = nTd.nextSibling;
				}
			}
			
			/* Now process by column */
			for ( iColumn=0, iColumns=oSettings.aoColumns.length ; iColumn<iColumns ; iColumn++ )
			{
				oCol = oSettings.aoColumns[iColumn];
		
				/* Get the title of the column - unless there is a user set one */
				if ( oCol.sTitle === null )
				{
					oCol.sTitle = oCol.nTh.innerHTML;
				}
				
				var
					bAutoType = oCol._bAutoType,
					bRender = typeof oCol.fnRender === 'function',
					bClass = oCol.sClass !== null,
					bVisible = oCol.bVisible,
					nCell, sThisType, sRendered, sValType;
				
				/* A single loop to rule them all (and be more efficient) */
				if ( bAutoType || bRender || bClass || !bVisible )
				{
					for ( iRow=0, iRows=oSettings.aoData.length ; iRow<iRows ; iRow++ )
					{
						oData = oSettings.aoData[iRow];
						nCell = nTds[ (iRow*iColumns) + iColumn ];
						
						/* Type detection */
						if ( bAutoType && oCol.sType != 'string' )
						{
							sValType = _fnGetCellData( oSettings, iRow, iColumn, 'type' );
							if ( sValType !== '' )
							{
								sThisType = _fnDetectType( sValType );
								if ( oCol.sType === null )
								{
									oCol.sType = sThisType;
								}
								else if ( oCol.sType != sThisType && 
								          oCol.sType != "html" )
								{
									/* String is always the 'fallback' option */
									oCol.sType = 'string';
								}
							}
						}
		
						if ( oCol.mRender )
						{
							// mRender has been defined, so we need to get the value and set it
							nCell.innerHTML = _fnGetCellData( oSettings, iRow, iColumn, 'display' );
						}
						else if ( oCol.mData !== iColumn )
						{
							// If mData is not the same as the column number, then we need to
							// get the dev set value. If it is the column, no point in wasting
							// time setting the value that is already there!
							nCell.innerHTML = _fnGetCellData( oSettings, iRow, iColumn, 'display' );
						}
						
						/* Rendering */
						if ( bRender )
						{
							sRendered = _fnRender( oSettings, iRow, iColumn );
							nCell.innerHTML = sRendered;
							if ( oCol.bUseRendered )
							{
								/* Use the rendered data for filtering / sorting */
								_fnSetCellData( oSettings, iRow, iColumn, sRendered );
							}
						}
						
						/* Classes */
						if ( bClass )
						{
							nCell.className += ' '+oCol.sClass;
						}
						
						/* Column visibility */
						if ( !bVisible )
						{
							oData._anHidden[iColumn] = nCell;
							nCell.parentNode.removeChild( nCell );
						}
						else
						{
							oData._anHidden[iColumn] = null;
						}
		
						if ( oCol.fnCreatedCell )
						{
							oCol.fnCreatedCell.call( oSettings.oInstance,
								nCell, _fnGetCellData( oSettings, iRow, iColumn, 'display' ), oData._aData, iRow, iColumn
							);
						}
					}
				}
			}
		
			/* Row created callbacks */
			if ( oSettings.aoRowCreatedCallback.length !== 0 )
			{
				for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
				{
					oData = oSettings.aoData[i];
					_fnCallbackFire( oSettings, 'aoRowCreatedCallback', null, [oData.nTr, oData._aData, i] );
				}
			}
		}
		
		
		/**
		 * Take a TR element and convert it to an index in aoData
		 *  @param {object} oSettings dataTables settings object
		 *  @param {node} n the TR element to find
		 *  @returns {int} index if the node is found, null if not
		 *  @memberof DataTable#oApi
		 */
		function _fnNodeToDataIndex( oSettings, n )
		{
			return (n._DT_RowIndex!==undefined) ? n._DT_RowIndex : null;
		}
		
		
		/**
		 * Take a TD element and convert it into a column data index (not the visible index)
		 *  @param {object} oSettings dataTables settings object
		 *  @param {int} iRow The row number the TD/TH can be found in
		 *  @param {node} n The TD/TH element to find
		 *  @returns {int} index if the node is found, -1 if not
		 *  @memberof DataTable#oApi
		 */
		function _fnNodeToColumnIndex( oSettings, iRow, n )
		{
			var anCells = _fnGetTdNodes( oSettings, iRow );
		
			for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
			{
				if ( anCells[i] === n )
				{
					return i;
				}
			}
			return -1;
		}
		
		
		/**
		 * Get an array of data for a given row from the internal data cache
		 *  @param {object} oSettings dataTables settings object
		 *  @param {int} iRow aoData row id
		 *  @param {string} sSpecific data get type ('type' 'filter' 'sort')
		 *  @param {array} aiColumns Array of column indexes to get data from
		 *  @returns {array} Data array
		 *  @memberof DataTable#oApi
		 */
		function _fnGetRowData( oSettings, iRow, sSpecific, aiColumns )
		{
			var out = [];
			for ( var i=0, iLen=aiColumns.length ; i<iLen ; i++ )
			{
				out.push( _fnGetCellData( oSettings, iRow, aiColumns[i], sSpecific ) );
			}
			return out;
		}
		
		
		/**
		 * Get the data for a given cell from the internal cache, taking into account data mapping
		 *  @param {object} oSettings dataTables settings object
		 *  @param {int} iRow aoData row id
		 *  @param {int} iCol Column index
		 *  @param {string} sSpecific data get type ('display', 'type' 'filter' 'sort')
		 *  @returns {*} Cell data
		 *  @memberof DataTable#oApi
		 */
		function _fnGetCellData( oSettings, iRow, iCol, sSpecific )
		{
			var sData;
			var oCol = oSettings.aoColumns[iCol];
			var oData = oSettings.aoData[iRow]._aData;
		
			if ( (sData=oCol.fnGetData( oData, sSpecific )) === undefined )
			{
				if ( oSettings.iDrawError != oSettings.iDraw && oCol.sDefaultContent === null )
				{
					_fnLog( oSettings, 0, "Requested unknown parameter "+
						(typeof oCol.mData=='function' ? '{mData function}' : "'"+oCol.mData+"'")+
						" from the data source for row "+iRow );
					oSettings.iDrawError = oSettings.iDraw;
				}
				return oCol.sDefaultContent;
			}
		
			/* When the data source is null, we can use default column data */
			if ( sData === null && oCol.sDefaultContent !== null )
			{
				sData = oCol.sDefaultContent;
			}
			else if ( typeof sData === 'function' )
			{
				/* If the data source is a function, then we run it and use the return */
				return sData();
			}
		
			if ( sSpecific == 'display' && sData === null )
			{
				return '';
			}
			return sData;
		}
		
		
		/**
		 * Set the value for a specific cell, into the internal data cache
		 *  @param {object} oSettings dataTables settings object
		 *  @param {int} iRow aoData row id
		 *  @param {int} iCol Column index
		 *  @param {*} val Value to set
		 *  @memberof DataTable#oApi
		 */
		function _fnSetCellData( oSettings, iRow, iCol, val )
		{
			var oCol = oSettings.aoColumns[iCol];
			var oData = oSettings.aoData[iRow]._aData;
		
			oCol.fnSetData( oData, val );
		}
		
		
		// Private variable that is used to match array syntax in the data property object
		var __reArray = /\[.*?\]$/;
		
		/**
		 * Return a function that can be used to get data from a source object, taking
		 * into account the ability to use nested objects as a source
		 *  @param {string|int|function} mSource The data source for the object
		 *  @returns {function} Data get function
		 *  @memberof DataTable#oApi
		 */
		function _fnGetObjectDataFn( mSource )
		{
			if ( mSource === null )
			{
				/* Give an empty string for rendering / sorting etc */
				return function (data, type) {
					return null;
				};
			}
			else if ( typeof mSource === 'function' )
			{
				return function (data, type, extra) {
					return mSource( data, type, extra );
				};
			}
			else if ( typeof mSource === 'string' && (mSource.indexOf('.') !== -1 || mSource.indexOf('[') !== -1) )
			{
				/* If there is a . in the source string then the data source is in a 
				 * nested object so we loop over the data for each level to get the next
				 * level down. On each loop we test for undefined, and if found immediately
				 * return. This allows entire objects to be missing and sDefaultContent to
				 * be used if defined, rather than throwing an error
				 */
				var fetchData = function (data, type, src) {
					var a = src.split('.');
					var arrayNotation, out, innerSrc;
		
					if ( src !== "" )
					{
						for ( var i=0, iLen=a.length ; i<iLen ; i++ )
						{
							// Check if we are dealing with an array notation request
							arrayNotation = a[i].match(__reArray);
		
							if ( arrayNotation ) {
								a[i] = a[i].replace(__reArray, '');
		
								// Condition allows simply [] to be passed in
								if ( a[i] !== "" ) {
									data = data[ a[i] ];
								}
								out = [];
								
								// Get the remainder of the nested object to get
								a.splice( 0, i+1 );
								innerSrc = a.join('.');
		
								// Traverse each entry in the array getting the properties requested
								for ( var j=0, jLen=data.length ; j<jLen ; j++ ) {
									out.push( fetchData( data[j], type, innerSrc ) );
								}
		
								// If a string is given in between the array notation indicators, that
								// is used to join the strings together, otherwise an array is returned
								var join = arrayNotation[0].substring(1, arrayNotation[0].length-1);
								data = (join==="") ? out : out.join(join);
		
								// The inner call to fetchData has already traversed through the remainder
								// of the source requested, so we exit from the loop
								break;
							}
		
							if ( data === null || data[ a[i] ] === undefined )
							{
								return undefined;
							}
							data = data[ a[i] ];
						}
					}
		
					return data;
				};
		
				return function (data, type) {
					return fetchData( data, type, mSource );
				};
			}
			else
			{
				/* Array or flat object mapping */
				return function (data, type) {
					return data[mSource];	
				};
			}
		}
		
		
		/**
		 * Return a function that can be used to set data from a source object, taking
		 * into account the ability to use nested objects as a source
		 *  @param {string|int|function} mSource The data source for the object
		 *  @returns {function} Data set function
		 *  @memberof DataTable#oApi
		 */
		function _fnSetObjectDataFn( mSource )
		{
			if ( mSource === null )
			{
				/* Nothing to do when the data source is null */
				return function (data, val) {};
			}
			else if ( typeof mSource === 'function' )
			{
				return function (data, val) {
					mSource( data, 'set', val );
				};
			}
			else if ( typeof mSource === 'string' && (mSource.indexOf('.') !== -1 || mSource.indexOf('[') !== -1) )
			{
				/* Like the get, we need to get data from a nested object */
				var setData = function (data, val, src) {
					var a = src.split('.'), b;
					var arrayNotation, o, innerSrc;
		
					for ( var i=0, iLen=a.length-1 ; i<iLen ; i++ )
					{
						// Check if we are dealing with an array notation request
						arrayNotation = a[i].match(__reArray);
		
						if ( arrayNotation )
						{
							a[i] = a[i].replace(__reArray, '');
							data[ a[i] ] = [];
							
							// Get the remainder of the nested object to set so we can recurse
							b = a.slice();
							b.splice( 0, i+1 );
							innerSrc = b.join('.');
		
							// Traverse each entry in the array setting the properties requested
							for ( var j=0, jLen=val.length ; j<jLen ; j++ )
							{
								o = {};
								setData( o, val[j], innerSrc );
								data[ a[i] ].push( o );
							}
		
							// The inner call to setData has already traversed through the remainder
							// of the source and has set the data, thus we can exit here
							return;
						}
		
						// If the nested object doesn't currently exist - since we are
						// trying to set the value - create it
						if ( data[ a[i] ] === null || data[ a[i] ] === undefined )
						{
							data[ a[i] ] = {};
						}
						data = data[ a[i] ];
					}
		
					// If array notation is used, we just want to strip it and use the property name
					// and assign the value. If it isn't used, then we get the result we want anyway
					data[ a[a.length-1].replace(__reArray, '') ] = val;
				};
		
				return function (data, val) {
					return setData( data, val, mSource );
				};
			}
			else
			{
				/* Array or flat object mapping */
				return function (data, val) {
					data[mSource] = val;	
				};
			}
		}
		
		
		/**
		 * Return an array with the full table data
		 *  @param {object} oSettings dataTables settings object
		 *  @returns array {array} aData Master data array
		 *  @memberof DataTable#oApi
		 */
		function _fnGetDataMaster ( oSettings )
		{
			var aData = [];
			var iLen = oSettings.aoData.length;
			for ( var i=0 ; i<iLen; i++ )
			{
				aData.push( oSettings.aoData[i]._aData );
			}
			return aData;
		}
		
		
		/**
		 * Nuke the table
		 *  @param {object} oSettings dataTables settings object
		 *  @memberof DataTable#oApi
		 */
		function _fnClearTable( oSettings )
		{
			oSettings.aoData.splice( 0, oSettings.aoData.length );
			oSettings.aiDisplayMaster.splice( 0, oSettings.aiDisplayMaster.length );
			oSettings.aiDisplay.splice( 0, oSettings.aiDisplay.length );
			_fnCalculateEnd( oSettings );
		}
		
		
		 /**
		 * Take an array of integers (index array) and remove a target integer (value - not 
		 * the key!)
		 *  @param {array} a Index array to target
		 *  @param {int} iTarget value to find
		 *  @memberof DataTable#oApi
		 */
		function _fnDeleteIndex( a, iTarget )
		{
			var iTargetIndex = -1;
			
			for ( var i=0, iLen=a.length ; i<iLen ; i++ )
			{
				if ( a[i] == iTarget )
				{
					iTargetIndex = i;
				}
				else if ( a[i] > iTarget )
				{
					a[i]--;
				}
			}
			
			if ( iTargetIndex != -1 )
			{
				a.splice( iTargetIndex, 1 );
			}
		}
		
		
		 /**
		 * Call the developer defined fnRender function for a given cell (row/column) with
		 * the required parameters and return the result.
		 *  @param {object} oSettings dataTables settings object
		 *  @param {int} iRow aoData index for the row
		 *  @param {int} iCol aoColumns index for the column
		 *  @returns {*} Return of the developer's fnRender function
		 *  @memberof DataTable#oApi
		 */
		function _fnRender( oSettings, iRow, iCol )
		{
			var oCol = oSettings.aoColumns[iCol];
		
			return oCol.fnRender( {
				"iDataRow":    iRow,
				"iDataColumn": iCol,
				"oSettings":   oSettings,
				"aData":       oSettings.aoData[iRow]._aData,
				"mDataProp":   oCol.mData
			}, _fnGetCellData(oSettings, iRow, iCol, 'display') );
		}
		/**
		 * Create a new TR element (and it's TD children) for a row
		 *  @param {object} oSettings dataTables settings object
		 *  @param {int} iRow Row to consider
		 *  @memberof DataTable#oApi
		 */
		function _fnCreateTr ( oSettings, iRow )
		{
			var oData = oSettings.aoData[iRow];
			var nTd;
		
			if ( oData.nTr === null )
			{
				oData.nTr = document.createElement('tr');
		
				/* Use a private property on the node to allow reserve mapping from the node
				 * to the aoData array for fast look up
				 */
				oData.nTr._DT_RowIndex = iRow;
		
				/* Special parameters can be given by the data source to be used on the row */
				if ( oData._aData.DT_RowId )
				{
					oData.nTr.id = oData._aData.DT_RowId;
				}
		
				if ( oData._aData.DT_RowClass )
				{
					oData.nTr.className = oData._aData.DT_RowClass;
				}
		
				/* Process each column */
				for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
				{
					var oCol = oSettings.aoColumns[i];
					nTd = document.createElement( oCol.sCellType );
		
					/* Render if needed - if bUseRendered is true then we already have the rendered
					 * value in the data source - so can just use that
					 */
					nTd.innerHTML = (typeof oCol.fnRender === 'function' && (!oCol.bUseRendered || oCol.mData === null)) ?
						_fnRender( oSettings, iRow, i ) :
						_fnGetCellData( oSettings, iRow, i, 'display' );
				
					/* Add user defined class */
					if ( oCol.sClass !== null )
					{
						nTd.className = oCol.sClass;
					}
					
					if ( oCol.bVisible )
					{
						oData.nTr.appendChild( nTd );
						oData._anHidden[i] = null;
					}
					else
					{
						oData._anHidden[i] = nTd;
					}
		
					if ( oCol.fnCreatedCell )
					{
						oCol.fnCreatedCell.call( oSettings.oInstance,
							nTd, _fnGetCellData( oSettings, iRow, i, 'display' ), oData._aData, iRow, i
						);
					}
				}
		
				_fnCallbackFire( oSettings, 'aoRowCreatedCallback', null, [oData.nTr, oData._aData, iRow] );
			}
		}
		
		
		/**
		 * Create the HTML header for the table
		 *  @param {object} oSettings dataTables settings object
		 *  @memberof DataTable#oApi
		 */
		function _fnBuildHead( oSettings )
		{
			var i, nTh, iLen, j, jLen;
			var iThs = $('th, td', oSettings.nTHead).length;
			var iCorrector = 0;
			var jqChildren;
			
			/* If there is a header in place - then use it - otherwise it's going to get nuked... */
			if ( iThs !== 0 )
			{
				/* We've got a thead from the DOM, so remove hidden columns and apply width to vis cols */
				for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
				{
					nTh = oSettings.aoColumns[i].nTh;
					nTh.setAttribute('role', 'columnheader');
					if ( oSettings.aoColumns[i].bSortable )
					{
						nTh.setAttribute('tabindex', oSettings.iTabIndex);
						nTh.setAttribute('aria-controls', oSettings.sTableId);
					}
		
					if ( oSettings.aoColumns[i].sClass !== null )
					{
						$(nTh).addClass( oSettings.aoColumns[i].sClass );
					}
					
					/* Set the title of the column if it is user defined (not what was auto detected) */
					if ( oSettings.aoColumns[i].sTitle != nTh.innerHTML )
					{
						nTh.innerHTML = oSettings.aoColumns[i].sTitle;
					}
				}
			}
			else
			{
				/* We don't have a header in the DOM - so we are going to have to create one */
				var nTr = document.createElement( "tr" );
				
				for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
				{
					nTh = oSettings.aoColumns[i].nTh;
					nTh.innerHTML = oSettings.aoColumns[i].sTitle;
					nTh.setAttribute('tabindex', '0');
					
					if ( oSettings.aoColumns[i].sClass !== null )
					{
						$(nTh).addClass( oSettings.aoColumns[i].sClass );
					}
					
					nTr.appendChild( nTh );
				}
				$(oSettings.nTHead).html( '' )[0].appendChild( nTr );
				_fnDetectHeader( oSettings.aoHeader, oSettings.nTHead );
			}
			
			/* ARIA role for the rows */	
			$(oSettings.nTHead).children('tr').attr('role', 'row');
			
			/* Add the extra markup needed by jQuery UI's themes */
			if ( oSettings.bJUI )
			{
				for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
				{
					nTh = oSettings.aoColumns[i].nTh;
					
					var nDiv = document.createElement('div');
					nDiv.className = oSettings.oClasses.sSortJUIWrapper;
					$(nTh).contents().appendTo(nDiv);
					
					var nSpan = document.createElement('span');
					nSpan.className = oSettings.oClasses.sSortIcon;
					nDiv.appendChild( nSpan );
					nTh.appendChild( nDiv );
				}
			}
			
			if ( oSettings.oFeatures.bSort )
			{
				for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
				{
					if ( oSettings.aoColumns[i].bSortable !== false )
					{
						_fnSortAttachListener( oSettings, oSettings.aoColumns[i].nTh, i );
					}
					else
					{
						$(oSettings.aoColumns[i].nTh).addClass( oSettings.oClasses.sSortableNone );
					}
				}
			}
			
			/* Deal with the footer - add classes if required */
			if ( oSettings.oClasses.sFooterTH !== "" )
			{
				$(oSettings.nTFoot).children('tr').children('th').addClass( oSettings.oClasses.sFooterTH );
			}
			
			/* Cache the footer elements */
			if ( oSettings.nTFoot !== null )
			{
				var anCells = _fnGetUniqueThs( oSettings, null, oSettings.aoFooter );
				for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
				{
					if ( anCells[i] )
					{
						oSettings.aoColumns[i].nTf = anCells[i];
						if ( oSettings.aoColumns[i].sClass )
						{
							$(anCells[i]).addClass( oSettings.aoColumns[i].sClass );
						}
					}
				}
			}
		}
		
		
		/**
		 * Draw the header (or footer) element based on the column visibility states. The
		 * methodology here is to use the layout array from _fnDetectHeader, modified for
		 * the instantaneous column visibility, to construct the new layout. The grid is
		 * traversed over cell at a time in a rows x columns grid fashion, although each 
		 * cell insert can cover multiple elements in the grid - which is tracks using the
		 * aApplied array. Cell inserts in the grid will only occur where there isn't
		 * already a cell in that position.
		 *  @param {object} oSettings dataTables settings object
		 *  @param array {objects} aoSource Layout array from _fnDetectHeader
		 *  @param {boolean} [bIncludeHidden=false] If true then include the hidden columns in the calc, 
		 *  @memberof DataTable#oApi
		 */
		function _fnDrawHead( oSettings, aoSource, bIncludeHidden )
		{
			var i, iLen, j, jLen, k, kLen, n, nLocalTr;
			var aoLocal = [];
			var aApplied = [];
			var iColumns = oSettings.aoColumns.length;
			var iRowspan, iColspan;
		
			if (  bIncludeHidden === undefined )
			{
				bIncludeHidden = false;
			}
		
			/* Make a copy of the master layout array, but without the visible columns in it */
			for ( i=0, iLen=aoSource.length ; i<iLen ; i++ )
			{
				aoLocal[i] = aoSource[i].slice();
				aoLocal[i].nTr = aoSource[i].nTr;
		
				/* Remove any columns which are currently hidden */
				for ( j=iColumns-1 ; j>=0 ; j-- )
				{
					if ( !oSettings.aoColumns[j].bVisible && !bIncludeHidden )
					{
						aoLocal[i].splice( j, 1 );
					}
				}
		
				/* Prep the applied array - it needs an element for each row */
				aApplied.push( [] );
			}
		
			for ( i=0, iLen=aoLocal.length ; i<iLen ; i++ )
			{
				nLocalTr = aoLocal[i].nTr;
				
				/* All cells are going to be replaced, so empty out the row */
				if ( nLocalTr )
				{
					while( (n = nLocalTr.firstChild) )
					{
						nLocalTr.removeChild( n );
					}
				}
		
				for ( j=0, jLen=aoLocal[i].length ; j<jLen ; j++ )
				{
					iRowspan = 1;
					iColspan = 1;
		
					/* Check to see if there is already a cell (row/colspan) covering our target
					 * insert point. If there is, then there is nothing to do.
					 */
					if ( aApplied[i][j] === undefined )
					{
						nLocalTr.appendChild( aoLocal[i][j].cell );
						aApplied[i][j] = 1;
		
						/* Expand the cell to cover as many rows as needed */
						while ( aoLocal[i+iRowspan] !== undefined &&
						        aoLocal[i][j].cell == aoLocal[i+iRowspan][j].cell )
						{
							aApplied[i+iRowspan][j] = 1;
							iRowspan++;
						}
		
						/* Expand the cell to cover as many columns as needed */
						while ( aoLocal[i][j+iColspan] !== undefined &&
						        aoLocal[i][j].cell == aoLocal[i][j+iColspan].cell )
						{
							/* Must update the applied array over the rows for the columns */
							for ( k=0 ; k<iRowspan ; k++ )
							{
								aApplied[i+k][j+iColspan] = 1;
							}
							iColspan++;
						}
		
						/* Do the actual expansion in the DOM */
						aoLocal[i][j].cell.rowSpan = iRowspan;
						aoLocal[i][j].cell.colSpan = iColspan;
					}
				}
			}
		}
		
		
		/**
		 * Insert the required TR nodes into the table for display
		 *  @param {object} oSettings dataTables settings object
		 *  @memberof DataTable#oApi
		 */
		function _fnDraw( oSettings )
		{
			/* Provide a pre-callback function which can be used to cancel the draw is false is returned */
			var aPreDraw = _fnCallbackFire( oSettings, 'aoPreDrawCallback', 'preDraw', [oSettings] );
			if ( $.inArray( false, aPreDraw ) !== -1 )
			{
				_fnProcessingDisplay( oSettings, false );
				return;
			}
			
			var i, iLen, n;
			var anRows = [];
			var iRowCount = 0;
			var iStripes = oSettings.asStripeClasses.length;
			var iOpenRows = oSettings.aoOpenRows.length;
			
			oSettings.bDrawing = true;
			
			/* Check and see if we have an initial draw position from state saving */
			if ( oSettings.iInitDisplayStart !== undefined && oSettings.iInitDisplayStart != -1 )
			{
				if ( oSettings.oFeatures.bServerSide )
				{
					oSettings._iDisplayStart = oSettings.iInitDisplayStart;
				}
				else
				{
					oSettings._iDisplayStart = (oSettings.iInitDisplayStart >= oSettings.fnRecordsDisplay()) ?
						0 : oSettings.iInitDisplayStart;
				}
				oSettings.iInitDisplayStart = -1;
				_fnCalculateEnd( oSettings );
			}
			
			/* Server-side processing draw intercept */
			if ( oSettings.bDeferLoading )
			{
				oSettings.bDeferLoading = false;
				oSettings.iDraw++;
			}
			else if ( !oSettings.oFeatures.bServerSide )
			{
				oSettings.iDraw++;
			}
			else if ( !oSettings.bDestroying && !_fnAjaxUpdate( oSettings ) )
			{
				return;
			}
			
			if ( oSettings.aiDisplay.length !== 0 )
			{
				var iStart = oSettings._iDisplayStart;
				var iEnd = oSettings._iDisplayEnd;
				
				if ( oSettings.oFeatures.bServerSide )
				{
					iStart = 0;
					iEnd = oSettings.aoData.length;
				}
				
				for ( var j=iStart ; j<iEnd ; j++ )
				{
					var aoData = oSettings.aoData[ oSettings.aiDisplay[j] ];
					if ( aoData.nTr === null )
					{
						_fnCreateTr( oSettings, oSettings.aiDisplay[j] );
					}
		
					var nRow = aoData.nTr;
					
					/* Remove the old striping classes and then add the new one */
					if ( iStripes !== 0 )
					{
						var sStripe = oSettings.asStripeClasses[ iRowCount % iStripes ];
						if ( aoData._sRowStripe != sStripe )
						{
							$(nRow).removeClass( aoData._sRowStripe ).addClass( sStripe );
							aoData._sRowStripe = sStripe;
						}
					}
					
					/* Row callback functions - might want to manipulate the row */
					_fnCallbackFire( oSettings, 'aoRowCallback', null, 
						[nRow, oSettings.aoData[ oSettings.aiDisplay[j] ]._aData, iRowCount, j] );
					
					anRows.push( nRow );
					iRowCount++;
					
					/* If there is an open row - and it is attached to this parent - attach it on redraw */
					if ( iOpenRows !== 0 )
					{
						for ( var k=0 ; k<iOpenRows ; k++ )
						{
							if ( nRow == oSettings.aoOpenRows[k].nParent )
							{
								anRows.push( oSettings.aoOpenRows[k].nTr );
								break;
							}
						}
					}
				}
			}
			else
			{
				/* Table is empty - create a row with an empty message in it */
				anRows[ 0 ] = document.createElement( 'tr' );
				
				if ( oSettings.asStripeClasses[0] )
				{
					anRows[ 0 ].className = oSettings.asStripeClasses[0];
				}
		
				var oLang = oSettings.oLanguage;
				var sZero = oLang.sZeroRecords;
				if ( oSettings.iDraw == 1 && oSettings.sAjaxSource !== null && !oSettings.oFeatures.bServerSide )
				{
					sZero = oLang.sLoadingRecords;
				}
				else if ( oLang.sEmptyTable && oSettings.fnRecordsTotal() === 0 )
				{
					sZero = oLang.sEmptyTable;
				}
		
				var nTd = document.createElement( 'td' );
				nTd.setAttribute( 'valign', "top" );
				nTd.colSpan = _fnVisbleColumns( oSettings );
				nTd.className = oSettings.oClasses.sRowEmpty;
				nTd.innerHTML = _fnInfoMacros( oSettings, sZero );
				
				anRows[ iRowCount ].appendChild( nTd );
			}
			
			/* Header and footer callbacks */
			_fnCallbackFire( oSettings, 'aoHeaderCallback', 'header', [ $(oSettings.nTHead).children('tr')[0], 
				_fnGetDataMaster( oSettings ), oSettings._iDisplayStart, oSettings.fnDisplayEnd(), oSettings.aiDisplay ] );
			
			_fnCallbackFire( oSettings, 'aoFooterCallback', 'footer', [ $(oSettings.nTFoot).children('tr')[0], 
				_fnGetDataMaster( oSettings ), oSettings._iDisplayStart, oSettings.fnDisplayEnd(), oSettings.aiDisplay ] );
			
			/* 
			 * Need to remove any old row from the display - note we can't just empty the tbody using
			 * $().html('') since this will unbind the jQuery event handlers (even although the node 
			 * still exists!) - equally we can't use innerHTML, since IE throws an exception.
			 */
			var
				nAddFrag = document.createDocumentFragment(),
				nRemoveFrag = document.createDocumentFragment(),
				nBodyPar, nTrs;
			
			if ( oSettings.nTBody )
			{
				nBodyPar = oSettings.nTBody.parentNode;
				nRemoveFrag.appendChild( oSettings.nTBody );
				
				/* When doing infinite scrolling, only remove child rows when sorting, filtering or start
				 * up. When not infinite scroll, always do it.
				 */
				if ( !oSettings.oScroll.bInfinite || !oSettings._bInitComplete ||
				 	oSettings.bSorted || oSettings.bFiltered )
				{
					while( (n = oSettings.nTBody.firstChild) )
					{
						oSettings.nTBody.removeChild( n );
					}
				}
				
				/* Put the draw table into the dom */
				for ( i=0, iLen=anRows.length ; i<iLen ; i++ )
				{
					nAddFrag.appendChild( anRows[i] );
				}
				
				oSettings.nTBody.appendChild( nAddFrag );
				if ( nBodyPar !== null )
				{
					nBodyPar.appendChild( oSettings.nTBody );
				}
			}
			
			/* Call all required callback functions for the end of a draw */
			_fnCallbackFire( oSettings, 'aoDrawCallback', 'draw', [oSettings] );
			
			/* Draw is complete, sorting and filtering must be as well */
			oSettings.bSorted = false;
			oSettings.bFiltered = false;
			oSettings.bDrawing = false;
			
			if ( oSettings.oFeatures.bServerSide )
			{
				_fnProcessingDisplay( oSettings, false );
				if ( !oSettings._bInitComplete )
				{
					_fnInitComplete( oSettings );
				}
			}
		}
		
		
		/**
		 * Redraw the table - taking account of the various features which are enabled
		 *  @param {object} oSettings dataTables settings object
		 *  @memberof DataTable#oApi
		 */
		function _fnReDraw( oSettings )
		{
			if ( oSettings.oFeatures.bSort )
			{
				/* Sorting will refilter and draw for us */
				_fnSort( oSettings, oSettings.oPreviousSearch );
			}
			else if ( oSettings.oFeatures.bFilter )
			{
				/* Filtering will redraw for us */
				_fnFilterComplete( oSettings, oSettings.oPreviousSearch );
			}
			else
			{
				_fnCalculateEnd( oSettings );
				_fnDraw( oSettings );
			}
		}
		
		
		/**
		 * Add the options to the page HTML for the table
		 *  @param {object} oSettings dataTables settings object
		 *  @memberof DataTable#oApi
		 */
		function _fnAddOptionsHtml ( oSettings )
		{
			/*
			 * Create a temporary, empty, div which we can later on replace with what we have generated
			 * we do it this way to rendering the 'options' html offline - speed :-)
			 */
			var nHolding = $('<div></div>')[0];
			oSettings.nTable.parentNode.insertBefore( nHolding, oSettings.nTable );
			
			/* 
			 * All DataTables are wrapped in a div
			 */
			oSettings.nTableWrapper = $('<div id="'+oSettings.sTableId+'_wrapper" class="'+oSettings.oClasses.sWrapper+'" role="grid"></div>')[0];
			oSettings.nTableReinsertBefore = oSettings.nTable.nextSibling;
		
			/* Track where we want to insert the option */
			var nInsertNode = oSettings.nTableWrapper;
			
			/* Loop over the user set positioning and place the elements as needed */
			var aDom = oSettings.sDom.split('');
			var nTmp, iPushFeature, cOption, nNewNode, cNext, sAttr, j;
			for ( var i=0 ; i<aDom.length ; i++ )
			{
				iPushFeature = 0;
				cOption = aDom[i];
				
				if ( cOption == '<' )
				{
					/* New container div */
					nNewNode = $('<div></div>')[0];
					
					/* Check to see if we should append an id and/or a class name to the container */
					cNext = aDom[i+1];
					if ( cNext == "'" || cNext == '"' )
					{
						sAttr = "";
						j = 2;
						while ( aDom[i+j] != cNext )
						{
							sAttr += aDom[i+j];
							j++;
						}
						
						/* Replace jQuery UI constants */
						if ( sAttr == "H" )
						{
							sAttr = oSettings.oClasses.sJUIHeader;
						}
						else if ( sAttr == "F" )
						{
							sAttr = oSettings.oClasses.sJUIFooter;
						}
						
						/* The attribute can be in the format of "#id.class", "#id" or "class" This logic
						 * breaks the string into parts and applies them as needed
						 */
						if ( sAttr.indexOf('.') != -1 )
						{
							var aSplit = sAttr.split('.');
							nNewNode.id = aSplit[0].substr(1, aSplit[0].length-1);
							nNewNode.className = aSplit[1];
						}
						else if ( sAttr.charAt(0) == "#" )
						{
							nNewNode.id = sAttr.substr(1, sAttr.length-1);
						}
						else
						{
							nNewNode.className = sAttr;
						}
						
						i += j; /* Move along the position array */
					}
					
					nInsertNode.appendChild( nNewNode );
					nInsertNode = nNewNode;
				}
				else if ( cOption == '>' )
				{
					/* End container div */
					nInsertNode = nInsertNode.parentNode;
				}
				else if ( cOption == 'l' && oSettings.oFeatures.bPaginate && oSettings.oFeatures.bLengthChange )
				{
					/* Length */
					nTmp = _fnFeatureHtmlLength( oSettings );
					iPushFeature = 1;
				}
				else if ( cOption == 'f' && oSettings.oFeatures.bFilter )
				{
					/* Filter */
					nTmp = _fnFeatureHtmlFilter( oSettings );
					iPushFeature = 1;
				}
				else if ( cOption == 'r' && oSettings.oFeatures.bProcessing )
				{
					/* pRocessing */
					nTmp = _fnFeatureHtmlProcessing( oSettings );
					iPushFeature = 1;
				}
				else if ( cOption == 't' )
				{
					/* Table */
					nTmp = _fnFeatureHtmlTable( oSettings );
					iPushFeature = 1;
				}
				else if ( cOption ==  'i' && oSettings.oFeatures.bInfo )
				{
					/* Info */
					nTmp = _fnFeatureHtmlInfo( oSettings );
					iPushFeature = 1;
				}
				else if ( cOption == 'p' && oSettings.oFeatures.bPaginate )
				{
					/* Pagination */
					nTmp = _fnFeatureHtmlPaginate( oSettings );
					iPushFeature = 1;
				}
				else if ( DataTable.ext.aoFeatures.length !== 0 )
				{
					/* Plug-in features */
					var aoFeatures = DataTable.ext.aoFeatures;
					for ( var k=0, kLen=aoFeatures.length ; k<kLen ; k++ )
					{
						if ( cOption == aoFeatures[k].cFeature )
						{
							nTmp = aoFeatures[k].fnInit( oSettings );
							if ( nTmp )
							{
								iPushFeature = 1;
							}
							break;
						}
					}
				}
				
				/* Add to the 2D features array */
				if ( iPushFeature == 1 && nTmp !== null )
				{
					if ( typeof oSettings.aanFeatures[cOption] !== 'object' )
					{
						oSettings.aanFeatures[cOption] = [];
					}
					oSettings.aanFeatures[cOption].push( nTmp );
					nInsertNode.appendChild( nTmp );
				}
			}
			
			/* Built our DOM structure - replace the holding div with what we want */
			nHolding.parentNode.replaceChild( oSettings.nTableWrapper, nHolding );
		}
		
		
		/**
		 * Use the DOM source to create up an array of header cells. The idea here is to
		 * create a layout grid (array) of rows x columns, which contains a reference
		 * to the cell that that point in the grid (regardless of col/rowspan), such that
		 * any column / row could be removed and the new grid constructed
		 *  @param array {object} aLayout Array to store the calculated layout in
		 *  @param {node} nThead The header/footer element for the table
		 *  @memberof DataTable#oApi
		 */
		function _fnDetectHeader ( aLayout, nThead )
		{
			var nTrs = $(nThead).children('tr');
			var nTr, nCell;
			var i, k, l, iLen, jLen, iColShifted, iColumn, iColspan, iRowspan;
			var bUnique;
			var fnShiftCol = function ( a, i, j ) {
				var k = a[i];
		                while ( k[j] ) {
					j++;
				}
				return j;
			};
		
			aLayout.splice( 0, aLayout.length );
			
			/* We know how many rows there are in the layout - so prep it */
			for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
			{
				aLayout.push( [] );
			}
			
			/* Calculate a layout array */
			for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
			{
				nTr = nTrs[i];
				iColumn = 0;
				
				/* For every cell in the row... */
				nCell = nTr.firstChild;
				while ( nCell ) {
					if ( nCell.nodeName.toUpperCase() == "TD" ||
					     nCell.nodeName.toUpperCase() == "TH" )
					{
						/* Get the col and rowspan attributes from the DOM and sanitise them */
						iColspan = nCell.getAttribute('colspan') * 1;
						iRowspan = nCell.getAttribute('rowspan') * 1;
						iColspan = (!iColspan || iColspan===0 || iColspan===1) ? 1 : iColspan;
						iRowspan = (!iRowspan || iRowspan===0 || iRowspan===1) ? 1 : iRowspan;
		
						/* There might be colspan cells already in this row, so shift our target 
						 * accordingly
						 */
						iColShifted = fnShiftCol( aLayout, i, iColumn );
						
						/* Cache calculation for unique columns */
						bUnique = iColspan === 1 ? true : false;
						
						/* If there is col / rowspan, copy the information into the layout grid */
						for ( l=0 ; l<iColspan ; l++ )
						{
							for ( k=0 ; k<iRowspan ; k++ )
							{
								aLayout[i+k][iColShifted+l] = {
									"cell": nCell,
									"unique": bUnique
								};
								aLayout[i+k].nTr = nTr;
							}
						}
					}
					nCell = nCell.nextSibling;
				}
			}
		}
		
		
		/**
		 * Get an array of unique th elements, one for each column
		 *  @param {object} oSettings dataTables settings object
		 *  @param {node} nHeader automatically detect the layout from this node - optional
		 *  @param {array} aLayout thead/tfoot layout from _fnDetectHeader - optional
		 *  @returns array {node} aReturn list of unique th's
		 *  @memberof DataTable#oApi
		 */
		function _fnGetUniqueThs ( oSettings, nHeader, aLayout )
		{
			var aReturn = [];
			if ( !aLayout )
			{
				aLayout = oSettings.aoHeader;
				if ( nHeader )
				{
					aLayout = [];
					_fnDetectHeader( aLayout, nHeader );
				}
			}
		
			for ( var i=0, iLen=aLayout.length ; i<iLen ; i++ )
			{
				for ( var j=0, jLen=aLayout[i].length ; j<jLen ; j++ )
				{
					if ( aLayout[i][j].unique && 
						 (!aReturn[j] || !oSettings.bSortCellsTop) )
					{
						aReturn[j] = aLayout[i][j].cell;
					}
				}
			}
			
			return aReturn;
		}
		
		
		
		/**
		 * Update the table using an Ajax call
		 *  @param {object} oSettings dataTables settings object
		 *  @returns {boolean} Block the table drawing or not
		 *  @memberof DataTable#oApi
		 */
		function _fnAjaxUpdate( oSettings )
		{
			if ( oSettings.bAjaxDataGet )
			{
				oSettings.iDraw++;
				_fnProcessingDisplay( oSettings, true );
				var iColumns = oSettings.aoColumns.length;
				var aoData = _fnAjaxParameters( oSettings );
				_fnServerParams( oSettings, aoData );
				
				oSettings.fnServerData.call( oSettings.oInstance, oSettings.sAjaxSource, aoData,
					function(json) {
						_fnAjaxUpdateDraw( oSettings, json );
					}, oSettings );
				return false;
			}
			else
			{
				return true;
			}
		}
		
		
		/**
		 * Build up the parameters in an object needed for a server-side processing request
		 *  @param {object} oSettings dataTables settings object
		 *  @returns {bool} block the table drawing or not
		 *  @memberof DataTable#oApi
		 */
		function _fnAjaxParameters( oSettings )
		{
			var iColumns = oSettings.aoColumns.length;
			var aoData = [], mDataProp, aaSort, aDataSort;
			var i, j;
			
			aoData.push( { "name": "sEcho",          "value": oSettings.iDraw } );
			aoData.push( { "name": "iColumns",       "value": iColumns } );
			aoData.push( { "name": "sColumns",       "value": _fnColumnOrdering(oSettings) } );
			aoData.push( { "name": "iDisplayStart",  "value": oSettings._iDisplayStart } );
			aoData.push( { "name": "iDisplayLength", "value": oSettings.oFeatures.bPaginate !== false ?
				oSettings._iDisplayLength : -1 } );
				
			for ( i=0 ; i<iColumns ; i++ )
			{
			  mDataProp = oSettings.aoColumns[i].mData;
				aoData.push( { "name": "mDataProp_"+i, "value": typeof(mDataProp)==="function" ? 'function' : mDataProp } );
			}
			
			/* Filtering */
			if ( oSettings.oFeatures.bFilter !== false )
			{
				aoData.push( { "name": "sSearch", "value": oSettings.oPreviousSearch.sSearch } );
				aoData.push( { "name": "bRegex",  "value": oSettings.oPreviousSearch.bRegex } );
				for ( i=0 ; i<iColumns ; i++ )
				{
					aoData.push( { "name": "sSearch_"+i,     "value": oSettings.aoPreSearchCols[i].sSearch } );
					aoData.push( { "name": "bRegex_"+i,      "value": oSettings.aoPreSearchCols[i].bRegex } );
					aoData.push( { "name": "bSearchable_"+i, "value": oSettings.aoColumns[i].bSearchable } );
				}
			}
			
			/* Sorting */
			if ( oSettings.oFeatures.bSort !== false )
			{
				var iCounter = 0;
		
				aaSort = ( oSettings.aaSortingFixed !== null ) ?
					oSettings.aaSortingFixed.concat( oSettings.aaSorting ) :
					oSettings.aaSorting.slice();
				
				for ( i=0 ; i<aaSort.length ; i++ )
				{
					aDataSort = oSettings.aoColumns[ aaSort[i][0] ].aDataSort;
					
					for ( j=0 ; j<aDataSort.length ; j++ )
					{
						aoData.push( { "name": "iSortCol_"+iCounter,  "value": aDataSort[j] } );
						aoData.push( { "name": "sSortDir_"+iCounter,  "value": aaSort[i][1] } );
						iCounter++;
					}
				}
				aoData.push( { "name": "iSortingCols",   "value": iCounter } );
				
				for ( i=0 ; i<iColumns ; i++ )
				{
					aoData.push( { "name": "bSortable_"+i,  "value": oSettings.aoColumns[i].bSortable } );
				}
			}
			
			return aoData;
		}
		
		
		/**
		 * Add Ajax parameters from plug-ins
		 *  @param {object} oSettings dataTables settings object
		 *  @param array {objects} aoData name/value pairs to send to the server
		 *  @memberof DataTable#oApi
		 */
		function _fnServerParams( oSettings, aoData )
		{
			_fnCallbackFire( oSettings, 'aoServerParams', 'serverParams', [aoData] );
		}
		
		
		/**
		 * Data the data from the server (nuking the old) and redraw the table
		 *  @param {object} oSettings dataTables settings object
		 *  @param {object} json json data return from the server.
		 *  @param {string} json.sEcho Tracking flag for DataTables to match requests
		 *  @param {int} json.iTotalRecords Number of records in the data set, not accounting for filtering
		 *  @param {int} json.iTotalDisplayRecords Number of records in the data set, accounting for filtering
		 *  @param {array} json.aaData The data to display on this page
		 *  @param {string} [json.sColumns] Column ordering (sName, comma separated)
		 *  @memberof DataTable#oApi
		 */
		function _fnAjaxUpdateDraw ( oSettings, json )
		{
			if ( json.sEcho !== undefined )
			{
				/* Protect against old returns over-writing a new one. Possible when you get
				 * very fast interaction, and later queries are completed much faster
				 */
				if ( json.sEcho*1 < oSettings.iDraw )
				{
					return;
				}
				else
				{
					oSettings.iDraw = json.sEcho * 1;
				}
			}
			
			if ( !oSettings.oScroll.bInfinite ||
				   (oSettings.oScroll.bInfinite && (oSettings.bSorted || oSettings.bFiltered)) )
			{
				_fnClearTable( oSettings );
			}
			oSettings._iRecordsTotal = parseInt(json.iTotalRecords, 10);
			oSettings._iRecordsDisplay = parseInt(json.iTotalDisplayRecords, 10);
			
			/* Determine if reordering is required */
			var sOrdering = _fnColumnOrdering(oSettings);
			var bReOrder = (json.sColumns !== undefined && sOrdering !== "" && json.sColumns != sOrdering );
			var aiIndex;
			if ( bReOrder )
			{
				aiIndex = _fnReOrderIndex( oSettings, json.sColumns );
			}
			
			var aData = _fnGetObjectDataFn( oSettings.sAjaxDataProp )( json );
			for ( var i=0, iLen=aData.length ; i<iLen ; i++ )
			{
				if ( bReOrder )
				{
					/* If we need to re-order, then create a new array with the correct order and add it */
					var aDataSorted = [];
					for ( var j=0, jLen=oSettings.aoColumns.length ; j<jLen ; j++ )
					{
						aDataSorted.push( aData[i][ aiIndex[j] ] );
					}
					_fnAddData( oSettings, aDataSorted );
				}
				else
				{
					/* No re-order required, sever got it "right" - just straight add */
					_fnAddData( oSettings, aData[i] );
				}
			}
			oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
			
			oSettings.bAjaxDataGet = false;
			_fnDraw( oSettings );
			oSettings.bAjaxDataGet = true;
			_fnProcessingDisplay( oSettings, false );
		}
		
		
		
		/**
		 * Generate the node required for filtering text
		 *  @returns {node} Filter control element
		 *  @param {object} oSettings dataTables settings object
		 *  @memberof DataTable#oApi
		 */
		function _fnFeatureHtmlFilter ( oSettings )
		{
			var oPreviousSearch = oSettings.oPreviousSearch;
			
			var sSearchStr = oSettings.oLanguage.sSearch;
			sSearchStr = (sSearchStr.indexOf('_INPUT_') !== -1) ?
			  sSearchStr.replace('_INPUT_', '<input type="text" />') :
			  sSearchStr==="" ? '<input type="text" />' : sSearchStr+' <input type="text" />';
			
			var nFilter = document.createElement( 'div' );
			nFilter.className = oSettings.oClasses.sFilter;
			nFilter.innerHTML = '<label>'+sSearchStr+'</label>';
			if ( !oSettings.aanFeatures.f )
			{
				nFilter.id = oSettings.sTableId+'_filter';
			}
			
			var jqFilter = $('input[type="text"]', nFilter);
		
			// Store a reference to the input element, so other input elements could be
			// added to the filter wrapper if needed (submit button for example)
			nFilter._DT_Input = jqFilter[0];
		
			jqFilter.val( oPreviousSearch.sSearch.replace('"','&quot;') );
			jqFilter.bind( 'keyup.DT', function(e) {
				/* Update all other filter input elements for the new display */
				var n = oSettings.aanFeatures.f;
				var val = this.value==="" ? "" : this.value; // mental IE8 fix :-(
		
				for ( var i=0, iLen=n.length ; i<iLen ; i++ )
				{
					if ( n[i] != $(this).parents('div.dataTables_filter')[0] )
					{
						$(n[i]._DT_Input).val( val );
					}
				}
				
				/* Now do the filter */
				if ( val != oPreviousSearch.sSearch )
				{
					_fnFilterComplete( oSettings, { 
						"sSearch": val, 
						"bRegex": oPreviousSearch.bRegex,
						"bSmart": oPreviousSearch.bSmart ,
						"bCaseInsensitive": oPreviousSearch.bCaseInsensitive 
					} );
				}
			} );
		
			jqFilter
				.attr('aria-controls', oSettings.sTableId)
				.bind( 'keypress.DT', function(e) {
					/* Prevent form submission */
					if ( e.keyCode == 13 )
					{
						return false;
					}
				}
			);
			
			return nFilter;
		}
		
		
		/**
		 * Filter the table using both the global filter and column based filtering
		 *  @param {object} oSettings dataTables settings object
		 *  @param {object} oSearch search information
		 *  @param {int} [iForce] force a research of the master array (1) or not (undefined or 0)
		 *  @memberof DataTable#oApi
		 */
		function _fnFilterComplete ( oSettings, oInput, iForce )
		{
			var oPrevSearch = oSettings.oPreviousSearch;
			var aoPrevSearch = oSettings.aoPreSearchCols;
			var fnSaveFilter = function ( oFilter ) {
				/* Save the filtering values */
				oPrevSearch.sSearch = oFilter.sSearch;
				oPrevSearch.bRegex = oFilter.bRegex;
				oPrevSearch.bSmart = oFilter.bSmart;
				oPrevSearch.bCaseInsensitive = oFilter.bCaseInsensitive;
			};
		
			/* In server-side processing all filtering is done by the server, so no point hanging around here */
			if ( !oSettings.oFeatures.bServerSide )
			{
				/* Global filter */
				_fnFilter( oSettings, oInput.sSearch, iForce, oInput.bRegex, oInput.bSmart, oInput.bCaseInsensitive );
				fnSaveFilter( oInput );
		
				/* Now do the individual column filter */
				for ( var i=0 ; i<oSettings.aoPreSearchCols.length ; i++ )
				{
					_fnFilterColumn( oSettings, aoPrevSearch[i].sSearch, i, aoPrevSearch[i].bRegex, 
						aoPrevSearch[i].bSmart, aoPrevSearch[i].bCaseInsensitive );
				}
				
				/* Custom filtering */
				_fnFilterCustom( oSettings );
			}
			else
			{
				fnSaveFilter( oInput );
			}
			
			/* Tell the draw function we have been filtering */
			oSettings.bFiltered = true;
			$(oSettings.oInstance).trigger('filter', oSettings);
			
			/* Redraw the table */
			oSettings._iDisplayStart = 0;
			_fnCalculateEnd( oSettings );
			_fnDraw( oSettings );
			
			/* Rebuild search array 'offline' */
			_fnBuildSearchArray( oSettings, 0 );
		}
		
		
		/**
		 * Apply custom filtering functions
		 *  @param {object} oSettings dataTables settings object
		 *  @memberof DataTable#oApi
		 */
		function _fnFilterCustom( oSettings )
		{
			var afnFilters = DataTable.ext.afnFiltering;
			var aiFilterColumns = _fnGetColumns( oSettings, 'bSearchable' );
		
			for ( var i=0, iLen=afnFilters.length ; i<iLen ; i++ )
			{
				var iCorrector = 0;
				for ( var j=0, jLen=oSettings.aiDisplay.length ; j<jLen ; j++ )
				{
					var iDisIndex = oSettings.aiDisplay[j-iCorrector];
					var bTest = afnFilters[i](
						oSettings,
						_fnGetRowData( oSettings, iDisIndex, 'filter', aiFilterColumns ),
						iDisIndex
					);
					
					/* Check if we should use this row based on the filtering function */
					if ( !bTest )
					{
						oSettings.aiDisplay.splice( j-iCorrector, 1 );
						iCorrector++;
					}
				}
			}
		}
		
		
		/**
		 * Filter the table on a per-column basis
		 *  @param {object} oSettings dataTables settings object
		 *  @param {string} sInput string to filter on
		 *  @param {int} iColumn column to filter
		 *  @param {bool} bRegex treat search string as a regular expression or not
		 *  @param {bool} bSmart use smart filtering or not
		 *  @param {bool} bCaseInsensitive Do case insenstive matching or not
		 *  @memberof DataTable#oApi
		 */
		function _fnFilterColumn ( oSettings, sInput, iColumn, bRegex, bSmart, bCaseInsensitive )
		{
			if ( sInput === "" )
			{
				return;
			}
			
			var iIndexCorrector = 0;
			var rpSearch = _fnFilterCreateSearch( sInput, bRegex, bSmart, bCaseInsensitive );
			
			for ( var i=oSettings.aiDisplay.length-1 ; i>=0 ; i-- )
			{
				var sData = _fnDataToSearch( _fnGetCellData( oSettings, oSettings.aiDisplay[i], iColumn, 'filter' ),
					oSettings.aoColumns[iColumn].sType );
				if ( ! rpSearch.test( sData ) )
				{
					oSettings.aiDisplay.splice( i, 1 );
					iIndexCorrector++;
				}
			}
		}
		
		
		/**
		 * Filter the data table based on user input and draw the table
		 *  @param {object} oSettings dataTables settings object
		 *  @param {string} sInput string to filter on
		 *  @param {int} iForce optional - force a research of the master array (1) or not (undefined or 0)
		 *  @param {bool} bRegex treat as a regular expression or not
		 *  @param {bool} bSmart perform smart filtering or not
		 *  @param {bool} bCaseInsensitive Do case insenstive matching or not
		 *  @memberof DataTable#oApi
		 */
		function _fnFilter( oSettings, sInput, iForce, bRegex, bSmart, bCaseInsensitive )
		{
			var i;
			var rpSearch = _fnFilterCreateSearch( sInput, bRegex, bSmart, bCaseInsensitive );
			var oPrevSearch = oSettings.oPreviousSearch;
			
			/* Check if we are forcing or not - optional parameter */
			if ( !iForce )
			{
				iForce = 0;
			}
			
			/* Need to take account of custom filtering functions - always filter */
			if ( DataTable.ext.afnFiltering.length !== 0 )
			{
				iForce = 1;
			}
			
			/*
			 * If the input is blank - we want the full data set
			 */
			if ( sInput.length <= 0 )
			{
				oSettings.aiDisplay.splice( 0, oSettings.aiDisplay.length);
				oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
			}
			else
			{
				/*
				 * We are starting a new search or the new search string is smaller 
				 * then the old one (i.e. delete). Search from the master array
			 	 */
				if ( oSettings.aiDisplay.length == oSettings.aiDisplayMaster.length ||
					   oPrevSearch.sSearch.length > sInput.length || iForce == 1 ||
					   sInput.indexOf(oPrevSearch.sSearch) !== 0 )
				{
					/* Nuke the old display array - we are going to rebuild it */
					oSettings.aiDisplay.splice( 0, oSettings.aiDisplay.length);
					
					/* Force a rebuild of the search array */
					_fnBuildSearchArray( oSettings, 1 );
					
					/* Search through all records to populate the search array
					 * The the oSettings.aiDisplayMaster and asDataSearch arrays have 1 to 1 
					 * mapping
					 */
					for ( i=0 ; i<oSettings.aiDisplayMaster.length ; i++ )
					{
						if ( rpSearch.test(oSettings.asDataSearch[i]) )
						{
							oSettings.aiDisplay.push( oSettings.aiDisplayMaster[i] );
						}
					}
			  }
			  else
				{
			  	/* Using old search array - refine it - do it this way for speed
			  	 * Don't have to search the whole master array again
					 */
			  	var iIndexCorrector = 0;
			  	
			  	/* Search the current results */
			  	for ( i=0 ; i<oSettings.asDataSearch.length ; i++ )
					{
			  		if ( ! rpSearch.test(oSettings.asDataSearch[i]) )
						{
			  			oSettings.aiDisplay.splice( i-iIndexCorrector, 1 );
			  			iIndexCorrector++;
			  		}
			  	}
			  }
			}
		}
		
		
		/**
		 * Create an array which can be quickly search through
		 *  @param {object} oSettings dataTables settings object
		 *  @param {int} iMaster use the master data array - optional
		 *  @memberof DataTable#oApi
		 */
		function _fnBuildSearchArray ( oSettings, iMaster )
		{
			if ( !oSettings.oFeatures.bServerSide )
			{
				/* Clear out the old data */
				oSettings.asDataSearch = [];
		
				var aiFilterColumns = _fnGetColumns( oSettings, 'bSearchable' );
				var aiIndex = (iMaster===1) ?
				 	oSettings.aiDisplayMaster :
				 	oSettings.aiDisplay;
				
				for ( var i=0, iLen=aiIndex.length ; i<iLen ; i++ )
				{
					oSettings.asDataSearch[i] = _fnBuildSearchRow(
						oSettings,
						_fnGetRowData( oSettings, aiIndex[i], 'filter', aiFilterColumns )
					);
				}
			}
		}
		
		
		/**
		 * Create a searchable string from a single data row
		 *  @param {object} oSettings dataTables settings object
		 *  @param {array} aData Row data array to use for the data to search
		 *  @memberof DataTable#oApi
		 */
		function _fnBuildSearchRow( oSettings, aData )
		{
			var sSearch = aData.join('  ');
			
			/* If it looks like there is an HTML entity in the string, attempt to decode it */
			if ( sSearch.indexOf('&') !== -1 )
			{
				sSearch = $('<div>').html(sSearch).text();
			}
			
			// Strip newline characters
			return sSearch.replace( /[\n\r]/g, " " );
		}
		
		/**
		 * Build a regular expression object suitable for searching a table
		 *  @param {string} sSearch string to search for
		 *  @param {bool} bRegex treat as a regular expression or not
		 *  @param {bool} bSmart perform smart filtering or not
		 *  @param {bool} bCaseInsensitive Do case insensitive matching or not
		 *  @returns {RegExp} constructed object
		 *  @memberof DataTable#oApi
		 */
		function _fnFilterCreateSearch( sSearch, bRegex, bSmart, bCaseInsensitive )
		{
			var asSearch, sRegExpString;
			
			if ( bSmart )
			{
				/* Generate the regular expression to use. Something along the lines of:
				 * ^(?=.*?\bone\b)(?=.*?\btwo\b)(?=.*?\bthree\b).*$
				 */
				asSearch = bRegex ? sSearch.split( ' ' ) : _fnEscapeRegex( sSearch ).split( ' ' );
				sRegExpString = '^(?=.*?'+asSearch.join( ')(?=.*?' )+').*$';
				return new RegExp( sRegExpString, bCaseInsensitive ? "i" : "" );
			}
			else
			{
				sSearch = bRegex ? sSearch : _fnEscapeRegex( sSearch );
				return new RegExp( sSearch, bCaseInsensitive ? "i" : "" );
			}
		}
		
		
		/**
		 * Convert raw data into something that the user can search on
		 *  @param {string} sData data to be modified
		 *  @param {string} sType data type
		 *  @returns {string} search string
		 *  @memberof DataTable#oApi
		 */
		function _fnDataToSearch ( sData, sType )
		{
			if ( typeof DataTable.ext.ofnSearch[sType] === "function" )
			{
				return DataTable.ext.ofnSearch[sType]( sData );
			}
			else if ( sData === null )
			{
				return '';
			}
			else if ( sType == "html" )
			{
				return sData.replace(/[\r\n]/g," ").replace( /<.*?>/g, "" );
			}
			else if ( typeof sData === "string" )
			{
				return sData.replace(/[\r\n]/g," ");
			}
			return sData;
		}
		
		
		/**
		 * scape a string such that it can be used in a regular expression
		 *  @param {string} sVal string to escape
		 *  @returns {string} escaped string
		 *  @memberof DataTable#oApi
		 */
		function _fnEscapeRegex ( sVal )
		{
			var acEscape = [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-' ];
			var reReplace = new RegExp( '(\\' + acEscape.join('|\\') + ')', 'g' );
			return sVal.replace(reReplace, '\\$1');
		}
		
		
		/**
		 * Generate the node required for the info display
		 *  @param {object} oSettings dataTables settings object
		 *  @returns {node} Information element
		 *  @memberof DataTable#oApi
		 */
		function _fnFeatureHtmlInfo ( oSettings )
		{
			var nInfo = document.createElement( 'div' );
			nInfo.className = oSettings.oClasses.sInfo;
			
			/* Actions that are to be taken once only for this feature */
			if ( !oSettings.aanFeatures.i )
			{
				/* Add draw callback */
				oSettings.aoDrawCallback.push( {
					"fn": _fnUpdateInfo,
					"sName": "information"
				} );
				
				/* Add id */
				nInfo.id = oSettings.sTableId+'_info';
			}
			oSettings.nTable.setAttribute( 'aria-describedby', oSettings.sTableId+'_info' );
			
			return nInfo;
		}
		
		
		/**
		 * Update the information elements in the display
		 *  @param {object} oSettings dataTables settings object
		 *  @memberof DataTable#oApi
		 */
		function _fnUpdateInfo ( oSettings )
		{
			/* Show information about the table */
			if ( !oSettings.oFeatures.bInfo || oSettings.aanFeatures.i.length === 0 )
			{
				return;
			}
			
			var
				oLang = oSettings.oLanguage,
				iStart = oSettings._iDisplayStart+1,
				iEnd = oSettings.fnDisplayEnd(),
				iMax = oSettings.fnRecordsTotal(),
				iTotal = oSettings.fnRecordsDisplay(),
				sOut;
			
			if ( iTotal === 0 )
			{
				/* Empty record set */
				sOut = oLang.sInfoEmpty;
			}
			else {
				/* Normal record set */
				sOut = oLang.sInfo;
			}
		
			if ( iTotal != iMax )
			{
				/* Record set after filtering */
				sOut += ' ' + oLang.sInfoFiltered;
			}
		
			// Convert the macros
			sOut += oLang.sInfoPostFix;
			sOut = _fnInfoMacros( oSettings, sOut );
			
			if ( oLang.fnInfoCallback !== null )
			{
				sOut = oLang.fnInfoCallback.call( oSettings.oInstance, 
					oSettings, iStart, iEnd, iMax, iTotal, sOut );
			}
			
			var n = oSettings.aanFeatures.i;
			for ( var i=0, iLen=n.length ; i<iLen ; i++ )
			{
				$(n[i]).html( sOut );
			}
		}
		
		
		function _fnInfoMacros ( oSettings, str )
		{
			var
				iStart = oSettings._iDisplayStart+1,
				sStart = oSettings.fnFormatNumber( iStart ),
				iEnd = oSettings.fnDisplayEnd(),
				sEnd = oSettings.fnFormatNumber( iEnd ),
				iTotal = oSettings.fnRecordsDisplay(),
				sTotal = oSettings.fnFormatNumber( iTotal ),
				iMax = oSettings.fnRecordsTotal(),
				sMax = oSettings.fnFormatNumber( iMax );
		
			// When infinite scrolling, we are always starting at 1. _iDisplayStart is used only
			// internally
			if ( oSettings.oScroll.bInfinite )
			{
				sStart = oSettings.fnFormatNumber( 1 );
			}
		
			return str.
				replace(/_START_/g, sStart).
				replace(/_END_/g,   sEnd).
				replace(/_TOTAL_/g, sTotal).
				replace(/_MAX_/g,   sMax);
		}
		
		
		
		/**
		 * Draw the table for the first time, adding all required features
		 *  @param {object} oSettings dataTables settings object
		 *  @memberof DataTable#oApi
		 */
		function _fnInitialise ( oSettings )
		{
			var i, iLen, iAjaxStart=oSettings.iInitDisplayStart;
			
			/* Ensure that the table data is fully initialised */
			if ( oSettings.bInitialised === false )
			{
				setTimeout( function(){ _fnInitialise( oSettings ); }, 200 );
				return;
			}
			
			/* Show the display HTML options */
			_fnAddOptionsHtml( oSettings );
			
			/* Build and draw the header / footer for the table */
			_fnBuildHead( oSettings );
			_fnDrawHead( oSettings, oSettings.aoHeader );
			if ( oSettings.nTFoot )
			{
				_fnDrawHead( oSettings, oSettings.aoFooter );
			}
		
			/* Okay to show that something is going on now */
			_fnProcessingDisplay( oSettings, true );
			
			/* Calculate sizes for columns */
			if ( oSettings.oFeatures.bAutoWidth )
			{
				_fnCalculateColumnWidths( oSettings );
			}
			
			for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
			{
				if ( oSettings.aoColumns[i].sWidth !== null )
				{
					oSettings.aoColumns[i].nTh.style.width = _fnStringToCss( oSettings.aoColumns[i].sWidth );
				}
			}
			
			/* If there is default sorting required - let's do it. The sort function will do the
			 * drawing for us. Otherwise we draw the table regardless of the Ajax source - this allows
			 * the table to look initialised for Ajax sourcing data (show 'loading' message possibly)
			 */
			if ( oSettings.oFeatures.bSort )
			{
				_fnSort( oSettings );
			}
			else if ( oSettings.oFeatures.bFilter )
			{
				_fnFilterComplete( oSettings, oSettings.oPreviousSearch );
			}
			else
			{
				oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
				_fnCalculateEnd( oSettings );
				_fnDraw( oSettings );
			}
			
			/* if there is an ajax source load the data */
			if ( oSettings.sAjaxSource !== null && !oSettings.oFeatures.bServerSide )
			{
				var aoData = [];
				_fnServerParams( oSettings, aoData );
				oSettings.fnServerData.call( oSettings.oInstance, oSettings.sAjaxSource, aoData, function(json) {
					var aData = (oSettings.sAjaxDataProp !== "") ?
					 	_fnGetObjectDataFn( oSettings.sAjaxDataProp )(json) : json;
		
					/* Got the data - add it to the table */
					for ( i=0 ; i<aData.length ; i++ )
					{
						_fnAddData( oSettings, aData[i] );
					}
					
					/* Reset the init display for cookie saving. We've already done a filter, and
					 * therefore cleared it before. So we need to make it appear 'fresh'
					 */
					oSettings.iInitDisplayStart = iAjaxStart;
					
					if ( oSettings.oFeatures.bSort )
					{
						_fnSort( oSettings );
					}
					else
					{
						oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
						_fnCalculateEnd( oSettings );
						_fnDraw( oSettings );
					}
					
					_fnProcessingDisplay( oSettings, false );
					_fnInitComplete( oSettings, json );
				}, oSettings );
				return;
			}
			
			/* Server-side processing initialisation complete is done at the end of _fnDraw */
			if ( !oSettings.oFeatures.bServerSide )
			{
				_fnProcessingDisplay( oSettings, false );
				_fnInitComplete( oSettings );
			}
		}
		
		
		/**
		 * Draw the table for the first time, adding all required features
		 *  @param {object} oSettings dataTables settings object
		 *  @param {object} [json] JSON from the server that completed the table, if using Ajax source
		 *    with client-side processing (optional)
		 *  @memberof DataTable#oApi
		 */
		function _fnInitComplete ( oSettings, json )
		{
			oSettings._bInitComplete = true;
			_fnCallbackFire( oSettings, 'aoInitComplete', 'init', [oSettings, json] );
		}
		
		
		/**
		 * Language compatibility - when certain options are given, and others aren't, we
		 * need to duplicate the values over, in order to provide backwards compatibility
		 * with older language files.
		 *  @param {object} oSettings dataTables settings object
		 *  @memberof DataTable#oApi
		 */
		function _fnLanguageCompat( oLanguage )
		{
			var oDefaults = DataTable.defaults.oLanguage;
		
			/* Backwards compatibility - if there is no sEmptyTable given, then use the same as
			 * sZeroRecords - assuming that is given.
			 */
			if ( !oLanguage.sEmptyTable && oLanguage.sZeroRecords &&
				oDefaults.sEmptyTable === "No data available in table" )
			{
				_fnMap( oLanguage, oLanguage, 'sZeroRecords', 'sEmptyTable' );
			}
		
			/* Likewise with loading records */
			if ( !oLanguage.sLoadingRecords && oLanguage.sZeroRecords &&
				oDefaults.sLoadingRecords === "Loading..." )
			{
				_fnMap( oLanguage, oLanguage, 'sZeroRecords', 'sLoadingRecords' );
			}
		}
		
		
		
		/**
		 * Generate the node required for user display length changing
		 *  @param {object} oSettings dataTables settings object
		 *  @returns {node} Display length feature node
		 *  @memberof DataTable#oApi
		 */
		function _fnFeatureHtmlLength ( oSettings )
		{
			if ( oSettings.oScroll.bInfinite )
			{
				return null;
			}
			
			/* This can be overruled by not using the _MENU_ var/macro in the language variable */
			var sName = 'name="'+oSettings.sTableId+'_length"';
			var sStdMenu = '<select size="1" '+sName+'>';
			var i, iLen;
			var aLengthMenu = oSettings.aLengthMenu;
			
			if ( aLengthMenu.length == 2 && typeof aLengthMenu[0] === 'object' && 
					typeof aLengthMenu[1] === 'object' )
			{
				for ( i=0, iLen=aLengthMenu[0].length ; i<iLen ; i++ )
				{
					sStdMenu += '<option value="'+aLengthMenu[0][i]+'">'+aLengthMenu[1][i]+'</option>';
				}
			}
			else
			{
				for ( i=0, iLen=aLengthMenu.length ; i<iLen ; i++ )
				{
					sStdMenu += '<option value="'+aLengthMenu[i]+'">'+aLengthMenu[i]+'</option>';
				}
			}
			sStdMenu += '</select>';
			
			var nLength = document.createElement( 'div' );
			if ( !oSettings.aanFeatures.l )
			{
				nLength.id = oSettings.sTableId+'_length';
			}
			nLength.className = oSettings.oClasses.sLength;
			nLength.innerHTML = '<label>'+oSettings.oLanguage.sLengthMenu.replace( '_MENU_', sStdMenu )+'</label>';
			
			/*
			 * Set the length to the current display length - thanks to Andrea Pavlovic for this fix,
			 * and Stefan Skopnik for fixing the fix!
			 */
			$('select option[value="'+oSettings._iDisplayLength+'"]', nLength).attr("selected", true);
			
			$('select', nLength).bind( 'change.DT', function(e) {
				var iVal = $(this).val();
				
				/* Update all other length options for the new display */
				var n = oSettings.aanFeatures.l;
				for ( i=0, iLen=n.length ; i<iLen ; i++ )
				{
					if ( n[i] != this.parentNode )
					{
						$('select', n[i]).val( iVal );
					}
				}
				
				/* Redraw the table */
				oSettings._iDisplayLength = parseInt(iVal, 10);
				_fnCalculateEnd( oSettings );
				
				/* If we have space to show extra rows (backing up from the end point - then do so */
				if ( oSettings.fnDisplayEnd() == oSettings.fnRecordsDisplay() )
				{
					oSettings._iDisplayStart = oSettings.fnDisplayEnd() - oSettings._iDisplayLength;
					if ( oSettings._iDisplayStart < 0 )
					{
						oSettings._iDisplayStart = 0;
					}
				}
				
				if ( oSettings._iDisplayLength == -1 )
				{
					oSettings._iDisplayStart = 0;
				}
				
				_fnDraw( oSettings );
			} );
		
		
			$('select', nLength).attr('aria-controls', oSettings.sTableId);
			
			return nLength;
		}
		
		
		/**
		 * Recalculate the end point based on the start point
		 *  @param {object} oSettings dataTables settings object
		 *  @memberof DataTable#oApi
		 */
		function _fnCalculateEnd( oSettings )
		{
			if ( oSettings.oFeatures.bPaginate === false )
			{
				oSettings._iDisplayEnd = oSettings.aiDisplay.length;
			}
			else
			{
				/* Set the end point of the display - based on how many elements there are
				 * still to display
				 */
				if ( oSettings._iDisplayStart + oSettings._iDisplayLength > oSettings.aiDisplay.length ||
					   oSettings._iDisplayLength == -1 )
				{
					oSettings._iDisplayEnd = oSettings.aiDisplay.length;
				}
				else
				{
					oSettings._iDisplayEnd = oSettings._iDisplayStart + oSettings._iDisplayLength;
				}
			}
		}
		
		
		
		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
		 * Note that most of the paging logic is done in 
		 * DataTable.ext.oPagination
		 */
		
		/**
		 * Generate the node required for default pagination
		 *  @param {object} oSettings dataTables settings object
		 *  @returns {node} Pagination feature node
		 *  @memberof DataTable#oApi
		 */
		function _fnFeatureHtmlPaginate ( oSettings )
		{
			if ( oSettings.oScroll.bInfinite )
			{
				return null;
			}
			
			var nPaginate = document.createElement( 'div' );
			nPaginate.className = oSettings.oClasses.sPaging+oSettings.sPaginationType;
			
			DataTable.ext.oPagination[ oSettings.sPaginationType ].fnInit( oSettings, nPaginate, 
				function( oSettings ) {
					_fnCalculateEnd( oSettings );
					_fnDraw( oSettings );
				}
			);
			
			/* Add a draw callback for the pagination on first instance, to update the paging display */
			if ( !oSettings.aanFeatures.p )
			{
				oSettings.aoDrawCallback.push( {
					"fn": function( oSettings ) {
						DataTable.ext.oPagination[ oSettings.sPaginationType ].fnUpdate( oSettings, function( oSettings ) {
							_fnCalculateEnd( oSettings );
							_fnDraw( oSettings );
						} );
					},
					"sName": "pagination"
				} );
			}
			return nPaginate;
		}
		
		
		/**
		 * Alter the display settings to change the page
		 *  @param {object} oSettings dataTables settings object
		 *  @param {string|int} mAction Paging action to take: "first", "previous", "next" or "last"
		 *    or page number to jump to (integer)
		 *  @returns {bool} true page has changed, false - no change (no effect) eg 'first' on page 1
		 *  @memberof DataTable#oApi
		 */
		function _fnPageChange ( oSettings, mAction )
		{
			var iOldStart = oSettings._iDisplayStart;
			
			if ( typeof mAction === "number" )
			{
				oSettings._iDisplayStart = mAction * oSettings._iDisplayLength;
				if ( oSettings._iDisplayStart > oSettings.fnRecordsDisplay() )
				{
					oSettings._iDisplayStart = 0;
				}
			}
			else if ( mAction == "first" )
			{
				oSettings._iDisplayStart = 0;
			}
			else if ( mAction == "previous" )
			{
				oSettings._iDisplayStart = oSettings._iDisplayLength>=0 ?
					oSettings._iDisplayStart - oSettings._iDisplayLength :
					0;
				
				/* Correct for under-run */
				if ( oSettings._iDisplayStart < 0 )
				{
				  oSettings._iDisplayStart = 0;
				}
			}
			else if ( mAction == "next" )
			{
				if ( oSettings._iDisplayLength >= 0 )
				{
					/* Make sure we are not over running the display array */
					if ( oSettings._iDisplayStart + oSettings._iDisplayLength < oSettings.fnRecordsDisplay() )
					{
						oSettings._iDisplayStart += oSettings._iDisplayLength;
					}
				}
				else
				{
					oSettings._iDisplayStart = 0;
				}
			}
			else if ( mAction == "last" )
			{
				if ( oSettings._iDisplayLength >= 0 )
				{
					var iPages = parseInt( (oSettings.fnRecordsDisplay()-1) / oSettings._iDisplayLength, 10 ) + 1;
					oSettings._iDisplayStart = (iPages-1) * oSettings._iDisplayLength;
				}
				else
				{
					oSettings._iDisplayStart = 0;
				}
			}
			else
			{
				_fnLog( oSettings, 0, "Unknown paging action: "+mAction );
			}
			$(oSettings.oInstance).trigger('page', oSettings);
			
			return iOldStart != oSettings._iDisplayStart;
		}
		
		
		
		/**
		 * Generate the node required for the processing node
		 *  @param {object} oSettings dataTables settings object
		 *  @returns {node} Processing element
		 *  @memberof DataTable#oApi
		 */
		function _fnFeatureHtmlProcessing ( oSettings )
		{
			var nProcessing = document.createElement( 'div' );
			
			if ( !oSettings.aanFeatures.r )
			{
				nProcessing.id = oSettings.sTableId+'_processing';
			}
			nProcessing.innerHTML = oSettings.oLanguage.sProcessing;
			nProcessing.className = oSettings.oClasses.sProcessing;
			oSettings.nTable.parentNode.insertBefore( nProcessing, oSettings.nTable );
			
			return nProcessing;
		}
		
		
		/**
		 * Display or hide the processing indicator
		 *  @param {object} oSettings dataTables settings object
		 *  @param {bool} bShow Show the processing indicator (true) or not (false)
		 *  @memberof DataTable#oApi
		 */
		function _fnProcessingDisplay ( oSettings, bShow )
		{
			if ( oSettings.oFeatures.bProcessing )
			{
				var an = oSettings.aanFeatures.r;
				for ( var i=0, iLen=an.length ; i<iLen ; i++ )
				{
					an[i].style.visibility = bShow ? "visible" : "hidden";
				}
			}
		
			$(oSettings.oInstance).trigger('processing', [oSettings, bShow]);
		}
		
		/**
		 * Add any control elements for the table - specifically scrolling
		 *  @param {object} oSettings dataTables settings object
		 *  @returns {node} Node to add to the DOM
		 *  @memberof DataTable#oApi
		 */
		function _fnFeatureHtmlTable ( oSettings )
		{
			/* Check if scrolling is enabled or not - if not then leave the DOM unaltered */
			if ( oSettings.oScroll.sX === "" && oSettings.oScroll.sY === "" )
			{
				return oSettings.nTable;
			}
			
			/*
			 * The HTML structure that we want to generate in this function is:
			 *  div - nScroller
			 *    div - nScrollHead
			 *      div - nScrollHeadInner
			 *        table - nScrollHeadTable
			 *          thead - nThead
			 *    div - nScrollBody
			 *      table - oSettings.nTable
			 *        thead - nTheadSize
			 *        tbody - nTbody
			 *    div - nScrollFoot
			 *      div - nScrollFootInner
			 *        table - nScrollFootTable
			 *          tfoot - nTfoot
			 */
			var
			 	nScroller = document.createElement('div'),
			 	nScrollHead = document.createElement('div'),
			 	nScrollHeadInner = document.createElement('div'),
			 	nScrollBody = document.createElement('div'),
			 	nScrollFoot = document.createElement('div'),
			 	nScrollFootInner = document.createElement('div'),
			 	nScrollHeadTable = oSettings.nTable.cloneNode(false),
			 	nScrollFootTable = oSettings.nTable.cloneNode(false),
				nThead = oSettings.nTable.getElementsByTagName('thead')[0],
			 	nTfoot = oSettings.nTable.getElementsByTagName('tfoot').length === 0 ? null : 
					oSettings.nTable.getElementsByTagName('tfoot')[0],
				oClasses = oSettings.oClasses;
			
			nScrollHead.appendChild( nScrollHeadInner );
			nScrollFoot.appendChild( nScrollFootInner );
			nScrollBody.appendChild( oSettings.nTable );
			nScroller.appendChild( nScrollHead );
			nScroller.appendChild( nScrollBody );
			nScrollHeadInner.appendChild( nScrollHeadTable );
			nScrollHeadTable.appendChild( nThead );
			if ( nTfoot !== null )
			{
				nScroller.appendChild( nScrollFoot );
				nScrollFootInner.appendChild( nScrollFootTable );
				nScrollFootTable.appendChild( nTfoot );
			}
			
			nScroller.className = oClasses.sScrollWrapper;
			nScrollHead.className = oClasses.sScrollHead;
			nScrollHeadInner.className = oClasses.sScrollHeadInner;
			nScrollBody.className = oClasses.sScrollBody;
			nScrollFoot.className = oClasses.sScrollFoot;
			nScrollFootInner.className = oClasses.sScrollFootInner;
			
			if ( oSettings.oScroll.bAutoCss )
			{
				nScrollHead.style.overflow = "hidden";
				nScrollHead.style.position = "relative";
				nScrollFoot.style.overflow = "hidden";
				nScrollBody.style.overflow = "auto";
			}
			
			nScrollHead.style.border = "0";
			nScrollHead.style.width = "100%";
			nScrollFoot.style.border = "0";
			nScrollHeadInner.style.width = oSettings.oScroll.sXInner !== "" ?
				oSettings.oScroll.sXInner : "100%"; /* will be overwritten */
			
			/* Modify attributes to respect the clones */
			nScrollHeadTable.removeAttribute('id');
			nScrollHeadTable.style.marginLeft = "0";
			oSettings.nTable.style.marginLeft = "0";
			if ( nTfoot !== null )
			{
				nScrollFootTable.removeAttribute('id');
				nScrollFootTable.style.marginLeft = "0";
			}
			
			/* Move caption elements from the body to the header, footer or leave where it is
			 * depending on the configuration. Note that the DTD says there can be only one caption */
			var nCaption = $(oSettings.nTable).children('caption');
			if ( nCaption.length > 0 )
			{
				nCaption = nCaption[0];
				if ( nCaption._captionSide === "top" )
				{
					nScrollHeadTable.appendChild( nCaption );
				}
				else if ( nCaption._captionSide === "bottom" && nTfoot )
				{
					nScrollFootTable.appendChild( nCaption );
				}
			}
			
			/*
			 * Sizing
			 */
			/* When x-scrolling add the width and a scroller to move the header with the body */
			if ( oSettings.oScroll.sX !== "" )
			{
				nScrollHead.style.width = _fnStringToCss( oSettings.oScroll.sX );
				nScrollBody.style.width = _fnStringToCss( oSettings.oScroll.sX );
				
				if ( nTfoot !== null )
				{
					nScrollFoot.style.width = _fnStringToCss( oSettings.oScroll.sX );	
				}
				
				/* When the body is scrolled, then we also want to scroll the headers */
				$(nScrollBody).scroll( function (e) {
					nScrollHead.scrollLeft = this.scrollLeft;
					
					if ( nTfoot !== null )
					{
						nScrollFoot.scrollLeft = this.scrollLeft;
					}
				} );
			}
			
			/* When yscrolling, add the height */
			if ( oSettings.oScroll.sY !== "" )
			{
				nScrollBody.style.height = _fnStringToCss( oSettings.oScroll.sY );
			}
			
			/* Redraw - align columns across the tables */
			oSettings.aoDrawCallback.push( {
				"fn": _fnScrollDraw,
				"sName": "scrolling"
			} );
			
			/* Infinite scrolling event handlers */
			if ( oSettings.oScroll.bInfinite )
			{
				$(nScrollBody).scroll( function() {
					/* Use a blocker to stop scrolling from loading more data while other data is still loading */
					if ( !oSettings.bDrawing && $(this).scrollTop() !== 0 )
					{
						/* Check if we should load the next data set */
						if ( $(this).scrollTop() + $(this).height() > 
							$(oSettings.nTable).height() - oSettings.oScroll.iLoadGap )
						{
							/* Only do the redraw if we have to - we might be at the end of the data */
							if ( oSettings.fnDisplayEnd() < oSettings.fnRecordsDisplay() )
							{
								_fnPageChange( oSettings, 'next' );
								_fnCalculateEnd( oSettings );
								_fnDraw( oSettings );
							}
						}
					}
				} );
			}
			
			oSettings.nScrollHead = nScrollHead;
			oSettings.nScrollFoot = nScrollFoot;
			
			return nScroller;
		}
		
		
		/**
		 * Update the various tables for resizing. It's a bit of a pig this function, but
		 * basically the idea to:
		 *   1. Re-create the table inside the scrolling div
		 *   2. Take live measurements from the DOM
		 *   3. Apply the measurements
		 *   4. Clean up
		 *  @param {object} o dataTables settings object
		 *  @returns {node} Node to add to the DOM
		 *  @memberof DataTable#oApi
		 */
		function _fnScrollDraw ( o )
		{
			var
				nScrollHeadInner = o.nScrollHead.getElementsByTagName('div')[0],
				nScrollHeadTable = nScrollHeadInner.getElementsByTagName('table')[0],
				nScrollBody = o.nTable.parentNode,
				i, iLen, j, jLen, anHeadToSize, anHeadSizers, anFootSizers, anFootToSize, oStyle, iVis,
				nTheadSize, nTfootSize,
				iWidth, aApplied=[], aAppliedFooter=[], iSanityWidth,
				nScrollFootInner = (o.nTFoot !== null) ? o.nScrollFoot.getElementsByTagName('div')[0] : null,
				nScrollFootTable = (o.nTFoot !== null) ? nScrollFootInner.getElementsByTagName('table')[0] : null,
				ie67 = o.oBrowser.bScrollOversize,
				zeroOut = function(nSizer) {
					oStyle = nSizer.style;
					oStyle.paddingTop = "0";
					oStyle.paddingBottom = "0";
					oStyle.borderTopWidth = "0";
					oStyle.borderBottomWidth = "0";
					oStyle.height = 0;
				};
			
			/*
			 * 1. Re-create the table inside the scrolling div
			 */
			
			/* Remove the old minimised thead and tfoot elements in the inner table */
			$(o.nTable).children('thead, tfoot').remove();
		
			/* Clone the current header and footer elements and then place it into the inner table */
			nTheadSize = $(o.nTHead).clone()[0];
			o.nTable.insertBefore( nTheadSize, o.nTable.childNodes[0] );
			anHeadToSize = o.nTHead.getElementsByTagName('tr');
			anHeadSizers = nTheadSize.getElementsByTagName('tr');
			
			if ( o.nTFoot !== null )
			{
				nTfootSize = $(o.nTFoot).clone()[0];
				o.nTable.insertBefore( nTfootSize, o.nTable.childNodes[1] );
				anFootToSize = o.nTFoot.getElementsByTagName('tr');
				anFootSizers = nTfootSize.getElementsByTagName('tr');
			}
			
			/*
			 * 2. Take live measurements from the DOM - do not alter the DOM itself!
			 */
			
			/* Remove old sizing and apply the calculated column widths
			 * Get the unique column headers in the newly created (cloned) header. We want to apply the
			 * calculated sizes to this header
			 */
			if ( o.oScroll.sX === "" )
			{
				nScrollBody.style.width = '100%';
				nScrollHeadInner.parentNode.style.width = '100%';
			}
			
			var nThs = _fnGetUniqueThs( o, nTheadSize );
			for ( i=0, iLen=nThs.length ; i<iLen ; i++ )
			{
				iVis = _fnVisibleToColumnIndex( o, i );
				nThs[i].style.width = o.aoColumns[iVis].sWidth;
			}
			
			if ( o.nTFoot !== null )
			{
				_fnApplyToChildren( function(n) {
					n.style.width = "";
				}, anFootSizers );
			}
		
			// If scroll collapse is enabled, when we put the headers back into the body for sizing, we
			// will end up forcing the scrollbar to appear, making our measurements wrong for when we
			// then hide it (end of this function), so add the header height to the body scroller.
			if ( o.oScroll.bCollapse && o.oScroll.sY !== "" )
			{
				nScrollBody.style.height = (nScrollBody.offsetHeight + o.nTHead.offsetHeight)+"px";
			}
			
			/* Size the table as a whole */
			iSanityWidth = $(o.nTable).outerWidth();
			if ( o.oScroll.sX === "" )
			{
				/* No x scrolling */
				o.nTable.style.width = "100%";
				
				/* I know this is rubbish - but IE7 will make the width of the table when 100% include
				 * the scrollbar - which is shouldn't. When there is a scrollbar we need to take this
				 * into account.
				 */
				if ( ie67 && ($('tbody', nScrollBody).height() > nScrollBody.offsetHeight || 
					$(nScrollBody).css('overflow-y') == "scroll")  )
				{
					o.nTable.style.width = _fnStringToCss( $(o.nTable).outerWidth() - o.oScroll.iBarWidth);
				}
			}
			else
			{
				if ( o.oScroll.sXInner !== "" )
				{
					/* x scroll inner has been given - use it */
					o.nTable.style.width = _fnStringToCss(o.oScroll.sXInner);
				}
				else if ( iSanityWidth == $(nScrollBody).width() &&
				   $(nScrollBody).height() < $(o.nTable).height() )
				{
					/* There is y-scrolling - try to take account of the y scroll bar */
					o.nTable.style.width = _fnStringToCss( iSanityWidth-o.oScroll.iBarWidth );
					if ( $(o.nTable).outerWidth() > iSanityWidth-o.oScroll.iBarWidth )
					{
						/* Not possible to take account of it */
						o.nTable.style.width = _fnStringToCss( iSanityWidth );
					}
				}
				else
				{
					/* All else fails */
					o.nTable.style.width = _fnStringToCss( iSanityWidth );
				}
			}
			
			/* Recalculate the sanity width - now that we've applied the required width, before it was
			 * a temporary variable. This is required because the column width calculation is done
			 * before this table DOM is created.
			 */
			iSanityWidth = $(o.nTable).outerWidth();
			
			/* We want the hidden header to have zero height, so remove padding and borders. Then
			 * set the width based on the real headers
			 */
			
			// Apply all styles in one pass. Invalidates layout only once because we don't read any 
			// DOM properties.
			_fnApplyToChildren( zeroOut, anHeadSizers );
			 
			// Read all widths in next pass. Forces layout only once because we do not change 
			// any DOM properties.
			_fnApplyToChildren( function(nSizer) {
				aApplied.push( _fnStringToCss( $(nSizer).width() ) );
			}, anHeadSizers );
			 
			// Apply all widths in final pass. Invalidates layout only once because we do not
			// read any DOM properties.
			_fnApplyToChildren( function(nToSize, i) {
				nToSize.style.width = aApplied[i];
			}, anHeadToSize );
		
			$(anHeadSizers).height(0);
			
			/* Same again with the footer if we have one */
			if ( o.nTFoot !== null )
			{
				_fnApplyToChildren( zeroOut, anFootSizers );
				 
				_fnApplyToChildren( function(nSizer) {
					aAppliedFooter.push( _fnStringToCss( $(nSizer).width() ) );
				}, anFootSizers );
				 
				_fnApplyToChildren( function(nToSize, i) {
					nToSize.style.width = aAppliedFooter[i];
				}, anFootToSize );
		
				$(anFootSizers).height(0);
			}
			
			/*
			 * 3. Apply the measurements
			 */
			
			/* "Hide" the header and footer that we used for the sizing. We want to also fix their width
			 * to what they currently are
			 */
			_fnApplyToChildren( function(nSizer, i) {
				nSizer.innerHTML = "";
				nSizer.style.width = aApplied[i];
			}, anHeadSizers );
			
			if ( o.nTFoot !== null )
			{
				_fnApplyToChildren( function(nSizer, i) {
					nSizer.innerHTML = "";
					nSizer.style.width = aAppliedFooter[i];
				}, anFootSizers );
			}
			
			/* Sanity check that the table is of a sensible width. If not then we are going to get
			 * misalignment - try to prevent this by not allowing the table to shrink below its min width
			 */
			if ( $(o.nTable).outerWidth() < iSanityWidth )
			{
				/* The min width depends upon if we have a vertical scrollbar visible or not */
				var iCorrection = ((nScrollBody.scrollHeight > nScrollBody.offsetHeight || 
					$(nScrollBody).css('overflow-y') == "scroll")) ?
						iSanityWidth+o.oScroll.iBarWidth : iSanityWidth;
				
				/* IE6/7 are a law unto themselves... */
				if ( ie67 && (nScrollBody.scrollHeight > 
					nScrollBody.offsetHeight || $(nScrollBody).css('overflow-y') == "scroll")  )
				{
					o.nTable.style.width = _fnStringToCss( iCorrection-o.oScroll.iBarWidth );
				}
				
				/* Apply the calculated minimum width to the table wrappers */
				nScrollBody.style.width = _fnStringToCss( iCorrection );
				o.nScrollHead.style.width = _fnStringToCss( iCorrection );
				
				if ( o.nTFoot !== null )
				{
					o.nScrollFoot.style.width = _fnStringToCss( iCorrection );
				}
				
				/* And give the user a warning that we've stopped the table getting too small */
				if ( o.oScroll.sX === "" )
				{
					_fnLog( o, 1, "The table cannot fit into the current element which will cause column"+
						" misalignment. The table has been drawn at its minimum possible width." );
				}
				else if ( o.oScroll.sXInner !== "" )
				{
					_fnLog( o, 1, "The table cannot fit into the current element which will cause column"+
						" misalignment. Increase the sScrollXInner value or remove it to allow automatic"+
						" calculation" );
				}
			}
			else
			{
				nScrollBody.style.width = _fnStringToCss( '100%' );
				o.nScrollHead.style.width = _fnStringToCss( '100%' );
				
				if ( o.nTFoot !== null )
				{
					o.nScrollFoot.style.width = _fnStringToCss( '100%' );
				}
			}
			
			
			/*
			 * 4. Clean up
			 */
			if ( o.oScroll.sY === "" )
			{
				/* IE7< puts a vertical scrollbar in place (when it shouldn't be) due to subtracting
				 * the scrollbar height from the visible display, rather than adding it on. We need to
				 * set the height in order to sort this. Don't want to do it in any other browsers.
				 */
				if ( ie67 )
				{
					nScrollBody.style.height = _fnStringToCss( o.nTable.offsetHeight+o.oScroll.iBarWidth );
				}
			}
			
			if ( o.oScroll.sY !== "" && o.oScroll.bCollapse )
			{
				nScrollBody.style.height = _fnStringToCss( o.oScroll.sY );
				
				var iExtra = (o.oScroll.sX !== "" && o.nTable.offsetWidth > nScrollBody.offsetWidth) ?
				 	o.oScroll.iBarWidth : 0;
				if ( o.nTable.offsetHeight < nScrollBody.offsetHeight )
				{
					nScrollBody.style.height = _fnStringToCss( o.nTable.offsetHeight+iExtra );
				}
			}
			
			/* Finally set the width's of the header and footer tables */
			var iOuterWidth = $(o.nTable).outerWidth();
			nScrollHeadTable.style.width = _fnStringToCss( iOuterWidth );
			nScrollHeadInner.style.width = _fnStringToCss( iOuterWidth );
		
			// Figure out if there are scrollbar present - if so then we need a the header and footer to
			// provide a bit more space to allow "overflow" scrolling (i.e. past the scrollbar)
			var bScrolling = $(o.nTable).height() > nScrollBody.clientHeight || $(nScrollBody).css('overflow-y') == "scroll";
			nScrollHeadInner.style.paddingRight = bScrolling ? o.oScroll.iBarWidth+"px" : "0px";
			
			if ( o.nTFoot !== null )
			{
				nScrollFootTable.style.width = _fnStringToCss( iOuterWidth );
				nScrollFootInner.style.width = _fnStringToCss( iOuterWidth );
				nScrollFootInner.style.paddingRight = bScrolling ? o.oScroll.iBarWidth+"px" : "0px";
			}
		
			/* Adjust the position of the header in case we loose the y-scrollbar */
			$(nScrollBody).scroll();
			
			/* If sorting or filtering has occurred, jump the scrolling back to the top */
			if ( o.bSorted || o.bFiltered )
			{
				nScrollBody.scrollTop = 0;
			}
		}
		
		
		/**
		 * Apply a given function to the display child nodes of an element array (typically
		 * TD children of TR rows
		 *  @param {function} fn Method to apply to the objects
		 *  @param array {nodes} an1 List of elements to look through for display children
		 *  @param array {nodes} an2 Another list (identical structure to the first) - optional
		 *  @memberof DataTable#oApi
		 */
		function _fnApplyToChildren( fn, an1, an2 )
		{
			var index=0, i=0, iLen=an1.length;
			var nNode1, nNode2;
		
			while ( i < iLen )
			{
				nNode1 = an1[i].firstChild;
				nNode2 = an2 ? an2[i].firstChild : null;
				while ( nNode1 )
				{
					if ( nNode1.nodeType === 1 )
					{
						if ( an2 )
						{
							fn( nNode1, nNode2, index );
						}
						else
						{
							fn( nNode1, index );
						}
						index++;
					}
					nNode1 = nNode1.nextSibling;
					nNode2 = an2 ? nNode2.nextSibling : null;
				}
				i++;
			}
		}
		
		/**
		 * Convert a CSS unit width to pixels (e.g. 2em)
		 *  @param {string} sWidth width to be converted
		 *  @param {node} nParent parent to get the with for (required for relative widths) - optional
		 *  @returns {int} iWidth width in pixels
		 *  @memberof DataTable#oApi
		 */
		function _fnConvertToWidth ( sWidth, nParent )
		{
			if ( !sWidth || sWidth === null || sWidth === '' )
			{
				return 0;
			}
			
			if ( !nParent )
			{
				nParent = document.body;
			}
			
			var iWidth;
			var nTmp = document.createElement( "div" );
			nTmp.style.width = _fnStringToCss( sWidth );
			
			nParent.appendChild( nTmp );
			iWidth = nTmp.offsetWidth;
			nParent.removeChild( nTmp );
			
			return ( iWidth );
		}
		
		
		/**
		 * Calculate the width of columns for the table
		 *  @param {object} oSettings dataTables settings object
		 *  @memberof DataTable#oApi
		 */
		function _fnCalculateColumnWidths ( oSettings )
		{
			var iTableWidth = oSettings.nTable.offsetWidth;
			var iUserInputs = 0;
			var iTmpWidth;
			var iVisibleColumns = 0;
			var iColums = oSettings.aoColumns.length;
			var i, iIndex, iCorrector, iWidth;
			var oHeaders = $('th', oSettings.nTHead);
			var widthAttr = oSettings.nTable.getAttribute('width');
			var nWrapper = oSettings.nTable.parentNode;
			
			/* Convert any user input sizes into pixel sizes */
			for ( i=0 ; i<iColums ; i++ )
			{
				if ( oSettings.aoColumns[i].bVisible )
				{
					iVisibleColumns++;
					
					if ( oSettings.aoColumns[i].sWidth !== null )
					{
						iTmpWidth = _fnConvertToWidth( oSettings.aoColumns[i].sWidthOrig, 
							nWrapper );
						if ( iTmpWidth !== null )
						{
							oSettings.aoColumns[i].sWidth = _fnStringToCss( iTmpWidth );
						}
							
						iUserInputs++;
					}
				}
			}
			
			/* If the number of columns in the DOM equals the number that we have to process in 
			 * DataTables, then we can use the offsets that are created by the web-browser. No custom 
			 * sizes can be set in order for this to happen, nor scrolling used
			 */
			if ( iColums == oHeaders.length && iUserInputs === 0 && iVisibleColumns == iColums &&
				oSettings.oScroll.sX === "" && oSettings.oScroll.sY === "" )
			{
				for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
				{
					iTmpWidth = $(oHeaders[i]).width();
					if ( iTmpWidth !== null )
					{
						oSettings.aoColumns[i].sWidth = _fnStringToCss( iTmpWidth );
					}
				}
			}
			else
			{
				/* Otherwise we are going to have to do some calculations to get the width of each column.
				 * Construct a 1 row table with the widest node in the data, and any user defined widths,
				 * then insert it into the DOM and allow the browser to do all the hard work of
				 * calculating table widths.
				 */
				var
					nCalcTmp = oSettings.nTable.cloneNode( false ),
					nTheadClone = oSettings.nTHead.cloneNode(true),
					nBody = document.createElement( 'tbody' ),
					nTr = document.createElement( 'tr' ),
					nDivSizing;
				
				nCalcTmp.removeAttribute( "id" );
				nCalcTmp.appendChild( nTheadClone );
				if ( oSettings.nTFoot !== null )
				{
					nCalcTmp.appendChild( oSettings.nTFoot.cloneNode(true) );
					_fnApplyToChildren( function(n) {
						n.style.width = "";
					}, nCalcTmp.getElementsByTagName('tr') );
				}
				
				nCalcTmp.appendChild( nBody );
				nBody.appendChild( nTr );
				
				/* Remove any sizing that was previously applied by the styles */
				var jqColSizing = $('thead th', nCalcTmp);
				if ( jqColSizing.length === 0 )
				{
					jqColSizing = $('tbody tr:eq(0)>td', nCalcTmp);
				}
		
				/* Apply custom sizing to the cloned header */
				var nThs = _fnGetUniqueThs( oSettings, nTheadClone );
				iCorrector = 0;
				for ( i=0 ; i<iColums ; i++ )
				{
					var oColumn = oSettings.aoColumns[i];
					if ( oColumn.bVisible && oColumn.sWidthOrig !== null && oColumn.sWidthOrig !== "" )
					{
						nThs[i-iCorrector].style.width = _fnStringToCss( oColumn.sWidthOrig );
					}
					else if ( oColumn.bVisible )
					{
						nThs[i-iCorrector].style.width = "";
					}
					else
					{
						iCorrector++;
					}
				}
		
				/* Find the biggest td for each column and put it into the table */
				for ( i=0 ; i<iColums ; i++ )
				{
					if ( oSettings.aoColumns[i].bVisible )
					{
						var nTd = _fnGetWidestNode( oSettings, i );
						if ( nTd !== null )
						{
							nTd = nTd.cloneNode(true);
							if ( oSettings.aoColumns[i].sContentPadding !== "" )
							{
								nTd.innerHTML += oSettings.aoColumns[i].sContentPadding;
							}
							nTr.appendChild( nTd );
						}
					}
				}
				
				/* Build the table and 'display' it */
				nWrapper.appendChild( nCalcTmp );
				
				/* When scrolling (X or Y) we want to set the width of the table as appropriate. However,
				 * when not scrolling leave the table width as it is. This results in slightly different,
				 * but I think correct behaviour
				 */
				if ( oSettings.oScroll.sX !== "" && oSettings.oScroll.sXInner !== "" )
				{
					nCalcTmp.style.width = _fnStringToCss(oSettings.oScroll.sXInner);
				}
				else if ( oSettings.oScroll.sX !== "" )
				{
					nCalcTmp.style.width = "";
					if ( $(nCalcTmp).width() < nWrapper.offsetWidth )
					{
						nCalcTmp.style.width = _fnStringToCss( nWrapper.offsetWidth );
					}
				}
				else if ( oSettings.oScroll.sY !== "" )
				{
					nCalcTmp.style.width = _fnStringToCss( nWrapper.offsetWidth );
				}
				else if ( widthAttr )
				{
					nCalcTmp.style.width = _fnStringToCss( widthAttr );
				}
				nCalcTmp.style.visibility = "hidden";
				
				/* Scrolling considerations */
				_fnScrollingWidthAdjust( oSettings, nCalcTmp );
				
				/* Read the width's calculated by the browser and store them for use by the caller. We
				 * first of all try to use the elements in the body, but it is possible that there are
				 * no elements there, under which circumstances we use the header elements
				 */
				var oNodes = $("tbody tr:eq(0)", nCalcTmp).children();
				if ( oNodes.length === 0 )
				{
					oNodes = _fnGetUniqueThs( oSettings, $('thead', nCalcTmp)[0] );
				}
		
				/* Browsers need a bit of a hand when a width is assigned to any columns when 
				 * x-scrolling as they tend to collapse the table to the min-width, even if
				 * we sent the column widths. So we need to keep track of what the table width
				 * should be by summing the user given values, and the automatic values
				 */
				if ( oSettings.oScroll.sX !== "" )
				{
					var iTotal = 0;
					iCorrector = 0;
					for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
					{
						if ( oSettings.aoColumns[i].bVisible )
						{
							if ( oSettings.aoColumns[i].sWidthOrig === null )
							{
								iTotal += $(oNodes[iCorrector]).outerWidth();
							}
							else
							{
								iTotal += parseInt(oSettings.aoColumns[i].sWidth.replace('px',''), 10) +
									($(oNodes[iCorrector]).outerWidth() - $(oNodes[iCorrector]).width());
							}
							iCorrector++;
						}
					}
					
					nCalcTmp.style.width = _fnStringToCss( iTotal );
					oSettings.nTable.style.width = _fnStringToCss( iTotal );
				}
		
				iCorrector = 0;
				for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
				{
					if ( oSettings.aoColumns[i].bVisible )
					{
						iWidth = $(oNodes[iCorrector]).width();
						if ( iWidth !== null && iWidth > 0 )
						{
							oSettings.aoColumns[i].sWidth = _fnStringToCss( iWidth );
						}
						iCorrector++;
					}
				}
		
				var cssWidth = $(nCalcTmp).css('width');
				oSettings.nTable.style.width = (cssWidth.indexOf('%') !== -1) ?
				    cssWidth : _fnStringToCss( $(nCalcTmp).outerWidth() );
				nCalcTmp.parentNode.removeChild( nCalcTmp );
			}
		
			if ( widthAttr )
			{
				oSettings.nTable.style.width = _fnStringToCss( widthAttr );
			}
		}
		
		
		/**
		 * Adjust a table's width to take account of scrolling
		 *  @param {object} oSettings dataTables settings object
		 *  @param {node} n table node
		 *  @memberof DataTable#oApi
		 */
		function _fnScrollingWidthAdjust ( oSettings, n )
		{
			if ( oSettings.oScroll.sX === "" && oSettings.oScroll.sY !== "" )
			{
				/* When y-scrolling only, we want to remove the width of the scroll bar so the table
				 * + scroll bar will fit into the area avaialble.
				 */
				var iOrigWidth = $(n).width();
				n.style.width = _fnStringToCss( $(n).outerWidth()-oSettings.oScroll.iBarWidth );
			}
			else if ( oSettings.oScroll.sX !== "" )
			{
				/* When x-scrolling both ways, fix the table at it's current size, without adjusting */
				n.style.width = _fnStringToCss( $(n).outerWidth() );
			}
		}
		
		
		/**
		 * Get the widest node
		 *  @param {object} oSettings dataTables settings object
		 *  @param {int} iCol column of interest
		 *  @returns {node} widest table node
		 *  @memberof DataTable#oApi
		 */
		function _fnGetWidestNode( oSettings, iCol )
		{
			var iMaxIndex = _fnGetMaxLenString( oSettings, iCol );
			if ( iMaxIndex < 0 )
			{
				return null;
			}
		
			if ( oSettings.aoData[iMaxIndex].nTr === null )
			{
				var n = document.createElement('td');
				n.innerHTML = _fnGetCellData( oSettings, iMaxIndex, iCol, '' );
				return n;
			}
			return _fnGetTdNodes(oSettings, iMaxIndex)[iCol];
		}
		
		
		/**
		 * Get the maximum strlen for each data column
		 *  @param {object} oSettings dataTables settings object
		 *  @param {int} iCol column of interest
		 *  @returns {string} max string length for each column
		 *  @memberof DataTable#oApi
		 */
		function _fnGetMaxLenString( oSettings, iCol )
		{
			var iMax = -1;
			var iMaxIndex = -1;
			
			for ( var i=0 ; i<oSettings.aoData.length ; i++ )
			{
				var s = _fnGetCellData( oSettings, i, iCol, 'display' )+"";
				s = s.replace( /<.*?>/g, "" );
				if ( s.length > iMax )
				{
					iMax = s.length;
					iMaxIndex = i;
				}
			}
			
			return iMaxIndex;
		}
		
		
		/**
		 * Append a CSS unit (only if required) to a string
		 *  @param {array} aArray1 first array
		 *  @param {array} aArray2 second array
		 *  @returns {int} 0 if match, 1 if length is different, 2 if no match
		 *  @memberof DataTable#oApi
		 */
		function _fnStringToCss( s )
		{
			if ( s === null )
			{
				return "0px";
			}
			
			if ( typeof s == 'number' )
			{
				if ( s < 0 )
				{
					return "0px";
				}
				return s+"px";
			}
			
			/* Check if the last character is not 0-9 */
			var c = s.charCodeAt( s.length-1 );
			if (c < 0x30 || c > 0x39)
			{
				return s;
			}
			return s+"px";
		}
		
		
		/**
		 * Get the width of a scroll bar in this browser being used
		 *  @returns {int} width in pixels
		 *  @memberof DataTable#oApi
		 */
		function _fnScrollBarWidth ()
		{  
			var inner = document.createElement('p');
			var style = inner.style;
			style.width = "100%";
			style.height = "200px";
			style.padding = "0px";
			
			var outer = document.createElement('div');
			style = outer.style;
			style.position = "absolute";
			style.top = "0px";
			style.left = "0px";
			style.visibility = "hidden";
			style.width = "200px";
			style.height = "150px";
			style.padding = "0px";
			style.overflow = "hidden";
			outer.appendChild(inner);
			
			document.body.appendChild(outer);
			var w1 = inner.offsetWidth;
			outer.style.overflow = 'scroll';
			var w2 = inner.offsetWidth;
			if ( w1 == w2 )
			{
				w2 = outer.clientWidth;
			}
			
			document.body.removeChild(outer);
			return (w1 - w2);  
		}
		
		/**
		 * Change the order of the table
		 *  @param {object} oSettings dataTables settings object
		 *  @param {bool} bApplyClasses optional - should we apply classes or not
		 *  @memberof DataTable#oApi
		 */
		function _fnSort ( oSettings, bApplyClasses )
		{
			var
				i, iLen, j, jLen, k, kLen,
				sDataType, nTh,
				aaSort = [],
			 	aiOrig = [],
				oSort = DataTable.ext.oSort,
				aoData = oSettings.aoData,
				aoColumns = oSettings.aoColumns,
				oAria = oSettings.oLanguage.oAria;
			
			/* No sorting required if server-side or no sorting array */
			if ( !oSettings.oFeatures.bServerSide && 
				(oSettings.aaSorting.length !== 0 || oSettings.aaSortingFixed !== null) )
			{
				aaSort = ( oSettings.aaSortingFixed !== null ) ?
					oSettings.aaSortingFixed.concat( oSettings.aaSorting ) :
					oSettings.aaSorting.slice();
				
				/* If there is a sorting data type, and a function belonging to it, then we need to
				 * get the data from the developer's function and apply it for this column
				 */
				for ( i=0 ; i<aaSort.length ; i++ )
				{
					var iColumn = aaSort[i][0];
					var iVisColumn = _fnColumnIndexToVisible( oSettings, iColumn );
					sDataType = oSettings.aoColumns[ iColumn ].sSortDataType;
					if ( DataTable.ext.afnSortData[sDataType] )
					{
						var aData = DataTable.ext.afnSortData[sDataType].call( 
							oSettings.oInstance, oSettings, iColumn, iVisColumn
						);
						if ( aData.length === aoData.length )
						{
							for ( j=0, jLen=aoData.length ; j<jLen ; j++ )
							{
								_fnSetCellData( oSettings, j, iColumn, aData[j] );
							}
						}
						else
						{
							_fnLog( oSettings, 0, "Returned data sort array (col "+iColumn+") is the wrong length" );
						}
					}
				}
				
				/* Create a value - key array of the current row positions such that we can use their
				 * current position during the sort, if values match, in order to perform stable sorting
				 */
				for ( i=0, iLen=oSettings.aiDisplayMaster.length ; i<iLen ; i++ )
				{
					aiOrig[ oSettings.aiDisplayMaster[i] ] = i;
				}
		
				/* Build an internal data array which is specific to the sort, so we can get and prep
				 * the data to be sorted only once, rather than needing to do it every time the sorting
				 * function runs. This make the sorting function a very simple comparison
				 */
				var iSortLen = aaSort.length;
				var fnSortFormat, aDataSort;
				for ( i=0, iLen=aoData.length ; i<iLen ; i++ )
				{
					for ( j=0 ; j<iSortLen ; j++ )
					{
						aDataSort = aoColumns[ aaSort[j][0] ].aDataSort;
		
						for ( k=0, kLen=aDataSort.length ; k<kLen ; k++ )
						{
							sDataType = aoColumns[ aDataSort[k] ].sType;
							fnSortFormat = oSort[ (sDataType ? sDataType : 'string')+"-pre" ];
							
							aoData[i]._aSortData[ aDataSort[k] ] = fnSortFormat ?
								fnSortFormat( _fnGetCellData( oSettings, i, aDataSort[k], 'sort' ) ) :
								_fnGetCellData( oSettings, i, aDataSort[k], 'sort' );
						}
					}
				}
				
				/* Do the sort - here we want multi-column sorting based on a given data source (column)
				 * and sorting function (from oSort) in a certain direction. It's reasonably complex to
				 * follow on it's own, but this is what we want (example two column sorting):
				 *  fnLocalSorting = function(a,b){
				 *  	var iTest;
				 *  	iTest = oSort['string-asc']('data11', 'data12');
				 *  	if (iTest !== 0)
				 *  		return iTest;
				 *    iTest = oSort['numeric-desc']('data21', 'data22');
				 *    if (iTest !== 0)
				 *  		return iTest;
				 *  	return oSort['numeric-asc']( aiOrig[a], aiOrig[b] );
				 *  }
				 * Basically we have a test for each sorting column, if the data in that column is equal,
				 * test the next column. If all columns match, then we use a numeric sort on the row 
				 * positions in the original data array to provide a stable sort.
				 */
				oSettings.aiDisplayMaster.sort( function ( a, b ) {
					var k, l, lLen, iTest, aDataSort, sDataType;
					for ( k=0 ; k<iSortLen ; k++ )
					{
						aDataSort = aoColumns[ aaSort[k][0] ].aDataSort;
		
						for ( l=0, lLen=aDataSort.length ; l<lLen ; l++ )
						{
							sDataType = aoColumns[ aDataSort[l] ].sType;
							
							iTest = oSort[ (sDataType ? sDataType : 'string')+"-"+aaSort[k][1] ](
								aoData[a]._aSortData[ aDataSort[l] ],
								aoData[b]._aSortData[ aDataSort[l] ]
							);
						
							if ( iTest !== 0 )
							{
								return iTest;
							}
						}
					}
					
					return oSort['numeric-asc']( aiOrig[a], aiOrig[b] );
				} );
			}
			
			/* Alter the sorting classes to take account of the changes */
			if ( (bApplyClasses === undefined || bApplyClasses) && !oSettings.oFeatures.bDeferRender )
			{
				_fnSortingClasses( oSettings );
			}
		
			for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
			{
				var sTitle = aoColumns[i].sTitle.replace( /<.*?>/g, "" );
				nTh = aoColumns[i].nTh;
				nTh.removeAttribute('aria-sort');
				nTh.removeAttribute('aria-label');
				
				/* In ARIA only the first sorting column can be marked as sorting - no multi-sort option */
				if ( aoColumns[i].bSortable )
				{
					if ( aaSort.length > 0 && aaSort[0][0] == i )
					{
						nTh.setAttribute('aria-sort', aaSort[0][1]=="asc" ? "ascending" : "descending" );
						
						var nextSort = (aoColumns[i].asSorting[ aaSort[0][2]+1 ]) ? 
							aoColumns[i].asSorting[ aaSort[0][2]+1 ] : aoColumns[i].asSorting[0];
						nTh.setAttribute('aria-label', sTitle+
							(nextSort=="asc" ? oAria.sSortAscending : oAria.sSortDescending) );
					}
					else
					{
						nTh.setAttribute('aria-label', sTitle+
							(aoColumns[i].asSorting[0]=="asc" ? oAria.sSortAscending : oAria.sSortDescending) );
					}
				}
				else
				{
					nTh.setAttribute('aria-label', sTitle);
				}
			}
			
			/* Tell the draw function that we have sorted the data */
			oSettings.bSorted = true;
			$(oSettings.oInstance).trigger('sort', oSettings);
			
			/* Copy the master data into the draw array and re-draw */
			if ( oSettings.oFeatures.bFilter )
			{
				/* _fnFilter() will redraw the table for us */
				_fnFilterComplete( oSettings, oSettings.oPreviousSearch, 1 );
			}
			else
			{
				oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
				oSettings._iDisplayStart = 0; /* reset display back to page 0 */
				_fnCalculateEnd( oSettings );
				_fnDraw( oSettings );
			}
		}
		
		
		/**
		 * Attach a sort handler (click) to a node
		 *  @param {object} oSettings dataTables settings object
		 *  @param {node} nNode node to attach the handler to
		 *  @param {int} iDataIndex column sorting index
		 *  @param {function} [fnCallback] callback function
		 *  @memberof DataTable#oApi
		 */
		function _fnSortAttachListener ( oSettings, nNode, iDataIndex, fnCallback )
		{
			_fnBindAction( nNode, {}, function (e) {
				/* If the column is not sortable - don't to anything */
				if ( oSettings.aoColumns[iDataIndex].bSortable === false )
				{
					return;
				}
				
				/*
				 * This is a little bit odd I admit... I declare a temporary function inside the scope of
				 * _fnBuildHead and the click handler in order that the code presented here can be used 
				 * twice - once for when bProcessing is enabled, and another time for when it is 
				 * disabled, as we need to perform slightly different actions.
				 *   Basically the issue here is that the Javascript engine in modern browsers don't 
				 * appear to allow the rendering engine to update the display while it is still executing
				 * it's thread (well - it does but only after long intervals). This means that the 
				 * 'processing' display doesn't appear for a table sort. To break the js thread up a bit
				 * I force an execution break by using setTimeout - but this breaks the expected 
				 * thread continuation for the end-developer's point of view (their code would execute
				 * too early), so we only do it when we absolutely have to.
				 */
				var fnInnerSorting = function () {
					var iColumn, iNextSort;
					
					/* If the shift key is pressed then we are multiple column sorting */
					if ( e.shiftKey )
					{
						/* Are we already doing some kind of sort on this column? */
						var bFound = false;
						for ( var i=0 ; i<oSettings.aaSorting.length ; i++ )
						{
							if ( oSettings.aaSorting[i][0] == iDataIndex )
							{
								bFound = true;
								iColumn = oSettings.aaSorting[i][0];
								iNextSort = oSettings.aaSorting[i][2]+1;
								
								if ( !oSettings.aoColumns[iColumn].asSorting[iNextSort] )
								{
									/* Reached the end of the sorting options, remove from multi-col sort */
									oSettings.aaSorting.splice( i, 1 );
								}
								else
								{
									/* Move onto next sorting direction */
									oSettings.aaSorting[i][1] = oSettings.aoColumns[iColumn].asSorting[iNextSort];
									oSettings.aaSorting[i][2] = iNextSort;
								}
								break;
							}
						}
						
						/* No sort yet - add it in */
						if ( bFound === false )
						{
							oSettings.aaSorting.push( [ iDataIndex, 
								oSettings.aoColumns[iDataIndex].asSorting[0], 0 ] );
						}
					}
					else
					{
						/* If no shift key then single column sort */
						if ( oSettings.aaSorting.length == 1 && oSettings.aaSorting[0][0] == iDataIndex )
						{
							iColumn = oSettings.aaSorting[0][0];
							iNextSort = oSettings.aaSorting[0][2]+1;
							if ( !oSettings.aoColumns[iColumn].asSorting[iNextSort] )
							{
								iNextSort = 0;
							}
							oSettings.aaSorting[0][1] = oSettings.aoColumns[iColumn].asSorting[iNextSort];
							oSettings.aaSorting[0][2] = iNextSort;
						}
						else
						{
							oSettings.aaSorting.splice( 0, oSettings.aaSorting.length );
							oSettings.aaSorting.push( [ iDataIndex, 
								oSettings.aoColumns[iDataIndex].asSorting[0], 0 ] );
						}
					}
					
					/* Run the sort */
					_fnSort( oSettings );
				}; /* /fnInnerSorting */
				
				if ( !oSettings.oFeatures.bProcessing )
				{
					fnInnerSorting();
				}
				else
				{
					_fnProcessingDisplay( oSettings, true );
					setTimeout( function() {
						fnInnerSorting();
						if ( !oSettings.oFeatures.bServerSide )
						{
							_fnProcessingDisplay( oSettings, false );
						}
					}, 0 );
				}
				
				/* Call the user specified callback function - used for async user interaction */
				if ( typeof fnCallback == 'function' )
				{
					fnCallback( oSettings );
				}
			} );
		}
		
		
		/**
		 * Set the sorting classes on the header, Note: it is safe to call this function 
		 * when bSort and bSortClasses are false
		 *  @param {object} oSettings dataTables settings object
		 *  @memberof DataTable#oApi
		 */
		function _fnSortingClasses( oSettings )
		{
			var i, iLen, j, jLen, iFound;
			var aaSort, sClass;
			var iColumns = oSettings.aoColumns.length;
			var oClasses = oSettings.oClasses;
			
			for ( i=0 ; i<iColumns ; i++ )
			{
				if ( oSettings.aoColumns[i].bSortable )
				{
					$(oSettings.aoColumns[i].nTh).removeClass( oClasses.sSortAsc +" "+ oClasses.sSortDesc +
						" "+ oSettings.aoColumns[i].sSortingClass );
				}
			}
			
			if ( oSettings.aaSortingFixed !== null )
			{
				aaSort = oSettings.aaSortingFixed.concat( oSettings.aaSorting );
			}
			else
			{
				aaSort = oSettings.aaSorting.slice();
			}
			
			/* Apply the required classes to the header */
			for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
			{
				if ( oSettings.aoColumns[i].bSortable )
				{
					sClass = oSettings.aoColumns[i].sSortingClass;
					iFound = -1;
					for ( j=0 ; j<aaSort.length ; j++ )
					{
						if ( aaSort[j][0] == i )
						{
							sClass = ( aaSort[j][1] == "asc" ) ?
								oClasses.sSortAsc : oClasses.sSortDesc;
							iFound = j;
							break;
						}
					}
					$(oSettings.aoColumns[i].nTh).addClass( sClass );
					
					if ( oSettings.bJUI )
					{
						/* jQuery UI uses extra markup */
						var jqSpan = $("span."+oClasses.sSortIcon,  oSettings.aoColumns[i].nTh);
						jqSpan.removeClass(oClasses.sSortJUIAsc +" "+ oClasses.sSortJUIDesc +" "+ 
							oClasses.sSortJUI +" "+ oClasses.sSortJUIAscAllowed +" "+ oClasses.sSortJUIDescAllowed );
						
						var sSpanClass;
						if ( iFound == -1 )
						{
						 	sSpanClass = oSettings.aoColumns[i].sSortingClassJUI;
						}
						else if ( aaSort[iFound][1] == "asc" )
						{
							sSpanClass = oClasses.sSortJUIAsc;
						}
						else
						{
							sSpanClass = oClasses.sSortJUIDesc;
						}
						
						jqSpan.addClass( sSpanClass );
					}
				}
				else
				{
					/* No sorting on this column, so add the base class. This will have been assigned by
					 * _fnAddColumn
					 */
					$(oSettings.aoColumns[i].nTh).addClass( oSettings.aoColumns[i].sSortingClass );
				}
			}
			
			/* 
			 * Apply the required classes to the table body
			 * Note that this is given as a feature switch since it can significantly slow down a sort
			 * on large data sets (adding and removing of classes is always slow at the best of times..)
			 * Further to this, note that this code is admittedly fairly ugly. It could be made a lot 
			 * simpler using jQuery selectors and add/removeClass, but that is significantly slower
			 * (on the order of 5 times slower) - hence the direct DOM manipulation here.
			 * Note that for deferred drawing we do use jQuery - the reason being that taking the first
			 * row found to see if the whole column needs processed can miss classes since the first
			 * column might be new.
			 */
			sClass = oClasses.sSortColumn;
			
			if ( oSettings.oFeatures.bSort && oSettings.oFeatures.bSortClasses )
			{
				var nTds = _fnGetTdNodes( oSettings );
				
				/* Determine what the sorting class for each column should be */
				var iClass, iTargetCol;
				var asClasses = [];
				for (i = 0; i < iColumns; i++)
				{
					asClasses.push("");
				}
				for (i = 0, iClass = 1; i < aaSort.length; i++)
				{
					iTargetCol = parseInt( aaSort[i][0], 10 );
					asClasses[iTargetCol] = sClass + iClass;
					
					if ( iClass < 3 )
					{
						iClass++;
					}
				}
				
				/* Make changes to the classes for each cell as needed */
				var reClass = new RegExp(sClass + "[123]");
				var sTmpClass, sCurrentClass, sNewClass;
				for ( i=0, iLen=nTds.length; i<iLen; i++ )
				{
					/* Determine which column we're looking at */
					iTargetCol = i % iColumns;
					
					/* What is the full list of classes now */
					sCurrentClass = nTds[i].className;
					/* What sorting class should be applied? */
					sNewClass = asClasses[iTargetCol];
					/* What would the new full list be if we did a replacement? */
					sTmpClass = sCurrentClass.replace(reClass, sNewClass);
					
					if ( sTmpClass != sCurrentClass )
					{
						/* We changed something */
						nTds[i].className = $.trim( sTmpClass );
					}
					else if ( sNewClass.length > 0 && sCurrentClass.indexOf(sNewClass) == -1 )
					{
						/* We need to add a class */
						nTds[i].className = sCurrentClass + " " + sNewClass;
					}
				}
			}
		}
		
		
		
		/**
		 * Save the state of a table in a cookie such that the page can be reloaded
		 *  @param {object} oSettings dataTables settings object
		 *  @memberof DataTable#oApi
		 */
		function _fnSaveState ( oSettings )
		{
			if ( !oSettings.oFeatures.bStateSave || oSettings.bDestroying )
			{
				return;
			}
		
			/* Store the interesting variables */
			var i, iLen, bInfinite=oSettings.oScroll.bInfinite;
			var oState = {
				"iCreate":      new Date().getTime(),
				"iStart":       (bInfinite ? 0 : oSettings._iDisplayStart),
				"iEnd":         (bInfinite ? oSettings._iDisplayLength : oSettings._iDisplayEnd),
				"iLength":      oSettings._iDisplayLength,
				"aaSorting":    $.extend( true, [], oSettings.aaSorting ),
				"oSearch":      $.extend( true, {}, oSettings.oPreviousSearch ),
				"aoSearchCols": $.extend( true, [], oSettings.aoPreSearchCols ),
				"abVisCols":    []
			};
		
			for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
			{
				oState.abVisCols.push( oSettings.aoColumns[i].bVisible );
			}
		
			_fnCallbackFire( oSettings, "aoStateSaveParams", 'stateSaveParams', [oSettings, oState] );
			
			oSettings.fnStateSave.call( oSettings.oInstance, oSettings, oState );
		}
		
		
		/**
		 * Attempt to load a saved table state from a cookie
		 *  @param {object} oSettings dataTables settings object
		 *  @param {object} oInit DataTables init object so we can override settings
		 *  @memberof DataTable#oApi
		 */
		function _fnLoadState ( oSettings, oInit )
		{
			if ( !oSettings.oFeatures.bStateSave )
			{
				return;
			}
		
			var oData = oSettings.fnStateLoad.call( oSettings.oInstance, oSettings );
			if ( !oData )
			{
				return;
			}
			
			/* Allow custom and plug-in manipulation functions to alter the saved data set and
			 * cancelling of loading by returning false
			 */
			var abStateLoad = _fnCallbackFire( oSettings, 'aoStateLoadParams', 'stateLoadParams', [oSettings, oData] );
			if ( $.inArray( false, abStateLoad ) !== -1 )
			{
				return;
			}
			
			/* Store the saved state so it might be accessed at any time */
			oSettings.oLoadedState = $.extend( true, {}, oData );
			
			/* Restore key features */
			oSettings._iDisplayStart    = oData.iStart;
			oSettings.iInitDisplayStart = oData.iStart;
			oSettings._iDisplayEnd      = oData.iEnd;
			oSettings._iDisplayLength   = oData.iLength;
			oSettings.aaSorting         = oData.aaSorting.slice();
			oSettings.saved_aaSorting   = oData.aaSorting.slice();
			
			/* Search filtering  */
			$.extend( oSettings.oPreviousSearch, oData.oSearch );
			$.extend( true, oSettings.aoPreSearchCols, oData.aoSearchCols );
			
			/* Column visibility state
			 * Pass back visibility settings to the init handler, but to do not here override
			 * the init object that the user might have passed in
			 */
			oInit.saved_aoColumns = [];
			for ( var i=0 ; i<oData.abVisCols.length ; i++ )
			{
				oInit.saved_aoColumns[i] = {};
				oInit.saved_aoColumns[i].bVisible = oData.abVisCols[i];
			}
		
			_fnCallbackFire( oSettings, 'aoStateLoaded', 'stateLoaded', [oSettings, oData] );
		}
		
		
		/**
		 * Create a new cookie with a value to store the state of a table
		 *  @param {string} sName name of the cookie to create
		 *  @param {string} sValue the value the cookie should take
		 *  @param {int} iSecs duration of the cookie
		 *  @param {string} sBaseName sName is made up of the base + file name - this is the base
		 *  @param {function} fnCallback User definable function to modify the cookie
		 *  @memberof DataTable#oApi
		 */
		function _fnCreateCookie ( sName, sValue, iSecs, sBaseName, fnCallback )
		{
			var date = new Date();
			date.setTime( date.getTime()+(iSecs*1000) );
			
			/* 
			 * Shocking but true - it would appear IE has major issues with having the path not having
			 * a trailing slash on it. We need the cookie to be available based on the path, so we
			 * have to append the file name to the cookie name. Appalling. Thanks to vex for adding the
			 * patch to use at least some of the path
			 */
			var aParts = window.location.pathname.split('/');
			var sNameFile = sName + '_' + aParts.pop().replace(/[\/:]/g,"").toLowerCase();
			var sFullCookie, oData;
			
			if ( fnCallback !== null )
			{
				oData = (typeof $.parseJSON === 'function') ? 
					$.parseJSON( sValue ) : eval( '('+sValue+')' );
				sFullCookie = fnCallback( sNameFile, oData, date.toGMTString(),
					aParts.join('/')+"/" );
			}
			else
			{
				sFullCookie = sNameFile + "=" + encodeURIComponent(sValue) +
					"; expires=" + date.toGMTString() +"; path=" + aParts.join('/')+"/";
			}
			
			/* Are we going to go over the cookie limit of 4KiB? If so, try to delete a cookies
			 * belonging to DataTables.
			 */
			var
				aCookies =document.cookie.split(';'),
				iNewCookieLen = sFullCookie.split(';')[0].length,
				aOldCookies = [];
			
			if ( iNewCookieLen+document.cookie.length+10 > 4096 ) /* Magic 10 for padding */
			{
				for ( var i=0, iLen=aCookies.length ; i<iLen ; i++ )
				{
					if ( aCookies[i].indexOf( sBaseName ) != -1 )
					{
						/* It's a DataTables cookie, so eval it and check the time stamp */
						var aSplitCookie = aCookies[i].split('=');
						try {
							oData = eval( '('+decodeURIComponent(aSplitCookie[1])+')' );
		
							if ( oData && oData.iCreate )
							{
								aOldCookies.push( {
									"name": aSplitCookie[0],
									"time": oData.iCreate
								} );
							}
						}
						catch( e ) {}
					}
				}
		
				// Make sure we delete the oldest ones first
				aOldCookies.sort( function (a, b) {
					return b.time - a.time;
				} );
		
				// Eliminate as many old DataTables cookies as we need to
				while ( iNewCookieLen + document.cookie.length + 10 > 4096 ) {
					if ( aOldCookies.length === 0 ) {
						// Deleted all DT cookies and still not enough space. Can't state save
						return;
					}
					
					var old = aOldCookies.pop();
					document.cookie = old.name+"=; expires=Thu, 01-Jan-1970 00:00:01 GMT; path="+
						aParts.join('/') + "/";
				}
			}
			
			document.cookie = sFullCookie;
		}
		
		
		/**
		 * Read an old cookie to get a cookie with an old table state
		 *  @param {string} sName name of the cookie to read
		 *  @returns {string} contents of the cookie - or null if no cookie with that name found
		 *  @memberof DataTable#oApi
		 */
		function _fnReadCookie ( sName )
		{
			var
				aParts = window.location.pathname.split('/'),
				sNameEQ = sName + '_' + aParts[aParts.length-1].replace(/[\/:]/g,"").toLowerCase() + '=',
			 	sCookieContents = document.cookie.split(';');
			
			for( var i=0 ; i<sCookieContents.length ; i++ )
			{
				var c = sCookieContents[i];
				
				while (c.charAt(0)==' ')
				{
					c = c.substring(1,c.length);
				}
				
				if (c.indexOf(sNameEQ) === 0)
				{
					return decodeURIComponent( c.substring(sNameEQ.length,c.length) );
				}
			}
			return null;
		}
		
		
		/**
		 * Return the settings object for a particular table
		 *  @param {node} nTable table we are using as a dataTable
		 *  @returns {object} Settings object - or null if not found
		 *  @memberof DataTable#oApi
		 */
		function _fnSettingsFromNode ( nTable )
		{
			for ( var i=0 ; i<DataTable.settings.length ; i++ )
			{
				if ( DataTable.settings[i].nTable === nTable )
				{
					return DataTable.settings[i];
				}
			}
			
			return null;
		}
		
		
		/**
		 * Return an array with the TR nodes for the table
		 *  @param {object} oSettings dataTables settings object
		 *  @returns {array} TR array
		 *  @memberof DataTable#oApi
		 */
		function _fnGetTrNodes ( oSettings )
		{
			var aNodes = [];
			var aoData = oSettings.aoData;
			for ( var i=0, iLen=aoData.length ; i<iLen ; i++ )
			{
				if ( aoData[i].nTr !== null )
				{
					aNodes.push( aoData[i].nTr );
				}
			}
			return aNodes;
		}
		
		
		/**
		 * Return an flat array with all TD nodes for the table, or row
		 *  @param {object} oSettings dataTables settings object
		 *  @param {int} [iIndividualRow] aoData index to get the nodes for - optional 
		 *    if not given then the return array will contain all nodes for the table
		 *  @returns {array} TD array
		 *  @memberof DataTable#oApi
		 */
		function _fnGetTdNodes ( oSettings, iIndividualRow )
		{
			var anReturn = [];
			var iCorrector;
			var anTds, nTd;
			var iRow, iRows=oSettings.aoData.length,
				iColumn, iColumns, oData, sNodeName, iStart=0, iEnd=iRows;
			
			/* Allow the collection to be limited to just one row */
			if ( iIndividualRow !== undefined )
			{
				iStart = iIndividualRow;
				iEnd = iIndividualRow+1;
			}
		
			for ( iRow=iStart ; iRow<iEnd ; iRow++ )
			{
				oData = oSettings.aoData[iRow];
				if ( oData.nTr !== null )
				{
					/* get the TD child nodes - taking into account text etc nodes */
					anTds = [];
					nTd = oData.nTr.firstChild;
					while ( nTd )
					{
						sNodeName = nTd.nodeName.toLowerCase();
						if ( sNodeName == 'td' || sNodeName == 'th' )
						{
							anTds.push( nTd );
						}
						nTd = nTd.nextSibling;
					}
		
					iCorrector = 0;
					for ( iColumn=0, iColumns=oSettings.aoColumns.length ; iColumn<iColumns ; iColumn++ )
					{
						if ( oSettings.aoColumns[iColumn].bVisible )
						{
							anReturn.push( anTds[iColumn-iCorrector] );
						}
						else
						{
							anReturn.push( oData._anHidden[iColumn] );
							iCorrector++;
						}
					}
				}
			}
		
			return anReturn;
		}
		
		
		/**
		 * Log an error message
		 *  @param {object} oSettings dataTables settings object
		 *  @param {int} iLevel log error messages, or display them to the user
		 *  @param {string} sMesg error message
		 *  @memberof DataTable#oApi
		 */
		function _fnLog( oSettings, iLevel, sMesg )
		{
			var sAlert = (oSettings===null) ?
				"DataTables warning: "+sMesg :
				"DataTables warning (table id = '"+oSettings.sTableId+"'): "+sMesg;
			
			if ( iLevel === 0 )
			{
				if ( DataTable.ext.sErrMode == 'alert' )
				{
					alert( sAlert );
				}
				else
				{
					throw new Error(sAlert);
				}
				return;
			}
			else if ( window.console && console.log )
			{
				console.log( sAlert );
			}
		}
		
		
		/**
		 * See if a property is defined on one object, if so assign it to the other object
		 *  @param {object} oRet target object
		 *  @param {object} oSrc source object
		 *  @param {string} sName property
		 *  @param {string} [sMappedName] name to map too - optional, sName used if not given
		 *  @memberof DataTable#oApi
		 */
		function _fnMap( oRet, oSrc, sName, sMappedName )
		{
			if ( sMappedName === undefined )
			{
				sMappedName = sName;
			}
			if ( oSrc[sName] !== undefined )
			{
				oRet[sMappedName] = oSrc[sName];
			}
		}
		
		
		/**
		 * Extend objects - very similar to jQuery.extend, but deep copy objects, and shallow
		 * copy arrays. The reason we need to do this, is that we don't want to deep copy array
		 * init values (such as aaSorting) since the dev wouldn't be able to override them, but
		 * we do want to deep copy arrays.
		 *  @param {object} oOut Object to extend
		 *  @param {object} oExtender Object from which the properties will be applied to oOut
		 *  @returns {object} oOut Reference, just for convenience - oOut === the return.
		 *  @memberof DataTable#oApi
		 *  @todo This doesn't take account of arrays inside the deep copied objects.
		 */
		function _fnExtend( oOut, oExtender )
		{
			var val;
			
			for ( var prop in oExtender )
			{
				if ( oExtender.hasOwnProperty(prop) )
				{
					val = oExtender[prop];
		
					if ( typeof oInit[prop] === 'object' && val !== null && $.isArray(val) === false )
					{
						$.extend( true, oOut[prop], val );
					}
					else
					{
						oOut[prop] = val;
					}
				}
			}
		
			return oOut;
		}
		
		
		/**
		 * Bind an event handers to allow a click or return key to activate the callback.
		 * This is good for accessibility since a return on the keyboard will have the
		 * same effect as a click, if the element has focus.
		 *  @param {element} n Element to bind the action to
		 *  @param {object} oData Data object to pass to the triggered function
		 *  @param {function} fn Callback function for when the event is triggered
		 *  @memberof DataTable#oApi
		 */
		function _fnBindAction( n, oData, fn )
		{
			$(n)
				.bind( 'click.DT', oData, function (e) {
						n.blur(); // Remove focus outline for mouse users
						fn(e);
					} )
				.bind( 'keypress.DT', oData, function (e){
					if ( e.which === 13 ) {
						fn(e);
					} } )
				.bind( 'selectstart.DT', function () {
					/* Take the brutal approach to cancelling text selection */
					return false;
					} );
		}
		
		
		/**
		 * Register a callback function. Easily allows a callback function to be added to
		 * an array store of callback functions that can then all be called together.
		 *  @param {object} oSettings dataTables settings object
		 *  @param {string} sStore Name of the array storage for the callbacks in oSettings
		 *  @param {function} fn Function to be called back
		 *  @param {string} sName Identifying name for the callback (i.e. a label)
		 *  @memberof DataTable#oApi
		 */
		function _fnCallbackReg( oSettings, sStore, fn, sName )
		{
			if ( fn )
			{
				oSettings[sStore].push( {
					"fn": fn,
					"sName": sName
				} );
			}
		}
		
		
		/**
		 * Fire callback functions and trigger events. Note that the loop over the callback
		 * array store is done backwards! Further note that you do not want to fire off triggers
		 * in time sensitive applications (for example cell creation) as its slow.
		 *  @param {object} oSettings dataTables settings object
		 *  @param {string} sStore Name of the array storage for the callbacks in oSettings
		 *  @param {string} sTrigger Name of the jQuery custom event to trigger. If null no trigger
		 *    is fired
		 *  @param {array} aArgs Array of arguments to pass to the callback function / trigger
		 *  @memberof DataTable#oApi
		 */
		function _fnCallbackFire( oSettings, sStore, sTrigger, aArgs )
		{
			var aoStore = oSettings[sStore];
			var aRet =[];
		
			for ( var i=aoStore.length-1 ; i>=0 ; i-- )
			{
				aRet.push( aoStore[i].fn.apply( oSettings.oInstance, aArgs ) );
			}
		
			if ( sTrigger !== null )
			{
				$(oSettings.oInstance).trigger(sTrigger, aArgs);
			}
		
			return aRet;
		}
		
		
		/**
		 * JSON stringify. If JSON.stringify it provided by the browser, json2.js or any other
		 * library, then we use that as it is fast, safe and accurate. If the function isn't 
		 * available then we need to built it ourselves - the inspiration for this function comes
		 * from Craig Buckler ( http://www.sitepoint.com/javascript-json-serialization/ ). It is
		 * not perfect and absolutely should not be used as a replacement to json2.js - but it does
		 * do what we need, without requiring a dependency for DataTables.
		 *  @param {object} o JSON object to be converted
		 *  @returns {string} JSON string
		 *  @memberof DataTable#oApi
		 */
		var _fnJsonString = (window.JSON) ? JSON.stringify : function( o )
		{
			/* Not an object or array */
			var sType = typeof o;
			if (sType !== "object" || o === null)
			{
				// simple data type
				if (sType === "string")
				{
					o = '"'+o+'"';
				}
				return o+"";
			}
		
			/* If object or array, need to recurse over it */
			var
				sProp, mValue,
				json = [],
				bArr = $.isArray(o);
			
			for (sProp in o)
			{
				mValue = o[sProp];
				sType = typeof mValue;
		
				if (sType === "string")
				{
					mValue = '"'+mValue+'"';
				}
				else if (sType === "object" && mValue !== null)
				{
					mValue = _fnJsonString(mValue);
				}
		
				json.push((bArr ? "" : '"'+sProp+'":') + mValue);
			}
		
			return (bArr ? "[" : "{") + json + (bArr ? "]" : "}");
		};
		
		
		/**
		 * From some browsers (specifically IE6/7) we need special handling to work around browser
		 * bugs - this function is used to detect when these workarounds are needed.
		 *  @param {object} oSettings dataTables settings object
		 *  @memberof DataTable#oApi
		 */
		function _fnBrowserDetect( oSettings )
		{
			/* IE6/7 will oversize a width 100% element inside a scrolling element, to include the
			 * width of the scrollbar, while other browsers ensure the inner element is contained
			 * without forcing scrolling
			 */
			var n = $(
				'<div style="position:absolute; top:0; left:0; height:1px; width:1px; overflow:hidden">'+
					'<div style="position:absolute; top:1px; left:1px; width:100px; overflow:scroll;">'+
						'<div id="DT_BrowserTest" style="width:100%; height:10px;"></div>'+
					'</div>'+
				'</div>')[0];
		
			document.body.appendChild( n );
			oSettings.oBrowser.bScrollOversize = $('#DT_BrowserTest', n)[0].offsetWidth === 100 ? true : false;
			document.body.removeChild( n );
		}
		

		/**
		 * Perform a jQuery selector action on the table's TR elements (from the tbody) and
		 * return the resulting jQuery object.
		 *  @param {string|node|jQuery} sSelector jQuery selector or node collection to act on
		 *  @param {object} [oOpts] Optional parameters for modifying the rows to be included
		 *  @param {string} [oOpts.filter=none] Select TR elements that meet the current filter
		 *    criterion ("applied") or all TR elements (i.e. no filter).
		 *  @param {string} [oOpts.order=current] Order of the TR elements in the processed array.
		 *    Can be either 'current', whereby the current sorting of the table is used, or
		 *    'original' whereby the original order the data was read into the table is used.
		 *  @param {string} [oOpts.page=all] Limit the selection to the currently displayed page
		 *    ("current") or not ("all"). If 'current' is given, then order is assumed to be 
		 *    'current' and filter is 'applied', regardless of what they might be given as.
		 *  @returns {object} jQuery object, filtered by the given selector.
		 *  @dtopt API
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Highlight every second row
		 *      oTable.$('tr:odd').css('backgroundColor', 'blue');
		 *    } );
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Filter to rows with 'Webkit' in them, add a background colour and then
		 *      // remove the filter, thus highlighting the 'Webkit' rows only.
		 *      oTable.fnFilter('Webkit');
		 *      oTable.$('tr', {"filter": "applied"}).css('backgroundColor', 'blue');
		 *      oTable.fnFilter('');
		 *    } );
		 */
		this.$ = function ( sSelector, oOpts )
		{
			var i, iLen, a = [], tr;
			var oSettings = _fnSettingsFromNode( this[DataTable.ext.iApiIndex] );
			var aoData = oSettings.aoData;
			var aiDisplay = oSettings.aiDisplay;
			var aiDisplayMaster = oSettings.aiDisplayMaster;
		
			if ( !oOpts )
			{
				oOpts = {};
			}
		
			oOpts = $.extend( {}, {
				"filter": "none", // applied
				"order": "current", // "original"
				"page": "all" // current
			}, oOpts );
		
			// Current page implies that order=current and fitler=applied, since it is fairly
			// senseless otherwise
			if ( oOpts.page == 'current' )
			{
				for ( i=oSettings._iDisplayStart, iLen=oSettings.fnDisplayEnd() ; i<iLen ; i++ )
				{
					tr = aoData[ aiDisplay[i] ].nTr;
					if ( tr )
					{
						a.push( tr );
					}
				}
			}
			else if ( oOpts.order == "current" && oOpts.filter == "none" )
			{
				for ( i=0, iLen=aiDisplayMaster.length ; i<iLen ; i++ )
				{
					tr = aoData[ aiDisplayMaster[i] ].nTr;
					if ( tr )
					{
						a.push( tr );
					}
				}
			}
			else if ( oOpts.order == "current" && oOpts.filter == "applied" )
			{
				for ( i=0, iLen=aiDisplay.length ; i<iLen ; i++ )
				{
					tr = aoData[ aiDisplay[i] ].nTr;
					if ( tr )
					{
						a.push( tr );
					}
				}
			}
			else if ( oOpts.order == "original" && oOpts.filter == "none" )
			{
				for ( i=0, iLen=aoData.length ; i<iLen ; i++ )
				{
					tr = aoData[ i ].nTr ;
					if ( tr )
					{
						a.push( tr );
					}
				}
			}
			else if ( oOpts.order == "original" && oOpts.filter == "applied" )
			{
				for ( i=0, iLen=aoData.length ; i<iLen ; i++ )
				{
					tr = aoData[ i ].nTr;
					if ( $.inArray( i, aiDisplay ) !== -1 && tr )
					{
						a.push( tr );
					}
				}
			}
			else
			{
				_fnLog( oSettings, 1, "Unknown selection options" );
			}
		
			/* We need to filter on the TR elements and also 'find' in their descendants
			 * to make the selector act like it would in a full table - so we need
			 * to build both results and then combine them together
			 */
			var jqA = $(a);
			var jqTRs = jqA.filter( sSelector );
			var jqDescendants = jqA.find( sSelector );
		
			return $( [].concat($.makeArray(jqTRs), $.makeArray(jqDescendants)) );
		};
		
		
		/**
		 * Almost identical to $ in operation, but in this case returns the data for the matched
		 * rows - as such, the jQuery selector used should match TR row nodes or TD/TH cell nodes
		 * rather than any descendants, so the data can be obtained for the row/cell. If matching
		 * rows are found, the data returned is the original data array/object that was used to  
		 * create the row (or a generated array if from a DOM source).
		 *
		 * This method is often useful in-combination with $ where both functions are given the
		 * same parameters and the array indexes will match identically.
		 *  @param {string|node|jQuery} sSelector jQuery selector or node collection to act on
		 *  @param {object} [oOpts] Optional parameters for modifying the rows to be included
		 *  @param {string} [oOpts.filter=none] Select elements that meet the current filter
		 *    criterion ("applied") or all elements (i.e. no filter).
		 *  @param {string} [oOpts.order=current] Order of the data in the processed array.
		 *    Can be either 'current', whereby the current sorting of the table is used, or
		 *    'original' whereby the original order the data was read into the table is used.
		 *  @param {string} [oOpts.page=all] Limit the selection to the currently displayed page
		 *    ("current") or not ("all"). If 'current' is given, then order is assumed to be 
		 *    'current' and filter is 'applied', regardless of what they might be given as.
		 *  @returns {array} Data for the matched elements. If any elements, as a result of the
		 *    selector, were not TR, TD or TH elements in the DataTable, they will have a null 
		 *    entry in the array.
		 *  @dtopt API
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Get the data from the first row in the table
		 *      var data = oTable._('tr:first');
		 *
		 *      // Do something useful with the data
		 *      alert( "First cell is: "+data[0] );
		 *    } );
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Filter to 'Webkit' and get all data for 
		 *      oTable.fnFilter('Webkit');
		 *      var data = oTable._('tr', {"filter": "applied"});
		 *      
		 *      // Do something with the data
		 *      alert( data.length+" rows matched the filter" );
		 *    } );
		 */
		this._ = function ( sSelector, oOpts )
		{
			var aOut = [];
			var i, iLen, iIndex;
			var aTrs = this.$( sSelector, oOpts );
		
			for ( i=0, iLen=aTrs.length ; i<iLen ; i++ )
			{
				aOut.push( this.fnGetData(aTrs[i]) );
			}
		
			return aOut;
		};
		
		
		/**
		 * Add a single new row or multiple rows of data to the table. Please note
		 * that this is suitable for client-side processing only - if you are using 
		 * server-side processing (i.e. "bServerSide": true), then to add data, you
		 * must add it to the data source, i.e. the server-side, through an Ajax call.
		 *  @param {array|object} mData The data to be added to the table. This can be:
		 *    <ul>
		 *      <li>1D array of data - add a single row with the data provided</li>
		 *      <li>2D array of arrays - add multiple rows in a single call</li>
		 *      <li>object - data object when using <i>mData</i></li>
		 *      <li>array of objects - multiple data objects when using <i>mData</i></li>
		 *    </ul>
		 *  @param {bool} [bRedraw=true] redraw the table or not
		 *  @returns {array} An array of integers, representing the list of indexes in 
		 *    <i>aoData</i> ({@link DataTable.models.oSettings}) that have been added to 
		 *    the table.
		 *  @dtopt API
		 *
		 *  @example
		 *    // Global var for counter
		 *    var giCount = 2;
		 *    
		 *    $(document).ready(function() {
		 *      $('#example').dataTable();
		 *    } );
		 *    
		 *    function fnClickAddRow() {
		 *      $('#example').dataTable().fnAddData( [
		 *        giCount+".1",
		 *        giCount+".2",
		 *        giCount+".3",
		 *        giCount+".4" ]
		 *      );
		 *        
		 *      giCount++;
		 *    }
		 */
		this.fnAddData = function( mData, bRedraw )
		{
			if ( mData.length === 0 )
			{
				return [];
			}
			
			var aiReturn = [];
			var iTest;
			
			/* Find settings from table node */
			var oSettings = _fnSettingsFromNode( this[DataTable.ext.iApiIndex] );
			
			/* Check if we want to add multiple rows or not */
			if ( typeof mData[0] === "object" && mData[0] !== null )
			{
				for ( var i=0 ; i<mData.length ; i++ )
				{
					iTest = _fnAddData( oSettings, mData[i] );
					if ( iTest == -1 )
					{
						return aiReturn;
					}
					aiReturn.push( iTest );
				}
			}
			else
			{
				iTest = _fnAddData( oSettings, mData );
				if ( iTest == -1 )
				{
					return aiReturn;
				}
				aiReturn.push( iTest );
			}
			
			oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
			
			if ( bRedraw === undefined || bRedraw )
			{
				_fnReDraw( oSettings );
			}
			return aiReturn;
		};
		
		
		/**
		 * This function will make DataTables recalculate the column sizes, based on the data 
		 * contained in the table and the sizes applied to the columns (in the DOM, CSS or 
		 * through the sWidth parameter). This can be useful when the width of the table's 
		 * parent element changes (for example a window resize).
		 *  @param {boolean} [bRedraw=true] Redraw the table or not, you will typically want to
		 *  @dtopt API
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable( {
		 *        "sScrollY": "200px",
		 *        "bPaginate": false
		 *      } );
		 *      
		 *      $(window).bind('resize', function () {
		 *        oTable.fnAdjustColumnSizing();
		 *      } );
		 *    } );
		 */
		this.fnAdjustColumnSizing = function ( bRedraw )
		{
			var oSettings = _fnSettingsFromNode(this[DataTable.ext.iApiIndex]);
			_fnAdjustColumnSizing( oSettings );
			
			if ( bRedraw === undefined || bRedraw )
			{
				this.fnDraw( false );
			}
			else if ( oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "" )
			{
				/* If not redrawing, but scrolling, we want to apply the new column sizes anyway */
				this.oApi._fnScrollDraw(oSettings);
			}
		};
		
		
		/**
		 * Quickly and simply clear a table
		 *  @param {bool} [bRedraw=true] redraw the table or not
		 *  @dtopt API
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *      
		 *      // Immediately 'nuke' the current rows (perhaps waiting for an Ajax callback...)
		 *      oTable.fnClearTable();
		 *    } );
		 */
		this.fnClearTable = function( bRedraw )
		{
			/* Find settings from table node */
			var oSettings = _fnSettingsFromNode( this[DataTable.ext.iApiIndex] );
			_fnClearTable( oSettings );
			
			if ( bRedraw === undefined || bRedraw )
			{
				_fnDraw( oSettings );
			}
		};
		
		
		/**
		 * The exact opposite of 'opening' a row, this function will close any rows which 
		 * are currently 'open'.
		 *  @param {node} nTr the table row to 'close'
		 *  @returns {int} 0 on success, or 1 if failed (can't find the row)
		 *  @dtopt API
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable;
		 *      
		 *      // 'open' an information row when a row is clicked on
		 *      $('#example tbody tr').click( function () {
		 *        if ( oTable.fnIsOpen(this) ) {
		 *          oTable.fnClose( this );
		 *        } else {
		 *          oTable.fnOpen( this, "Temporary row opened", "info_row" );
		 *        }
		 *      } );
		 *      
		 *      oTable = $('#example').dataTable();
		 *    } );
		 */
		this.fnClose = function( nTr )
		{
			/* Find settings from table node */
			var oSettings = _fnSettingsFromNode( this[DataTable.ext.iApiIndex] );
			
			for ( var i=0 ; i<oSettings.aoOpenRows.length ; i++ )
			{
				if ( oSettings.aoOpenRows[i].nParent == nTr )
				{
					var nTrParent = oSettings.aoOpenRows[i].nTr.parentNode;
					if ( nTrParent )
					{
						/* Remove it if it is currently on display */
						nTrParent.removeChild( oSettings.aoOpenRows[i].nTr );
					}
					oSettings.aoOpenRows.splice( i, 1 );
					return 0;
				}
			}
			return 1;
		};
		
		
		/**
		 * Remove a row for the table
		 *  @param {mixed} mTarget The index of the row from aoData to be deleted, or
		 *    the TR element you want to delete
		 *  @param {function|null} [fnCallBack] Callback function
		 *  @param {bool} [bRedraw=true] Redraw the table or not
		 *  @returns {array} The row that was deleted
		 *  @dtopt API
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *      
		 *      // Immediately remove the first row
		 *      oTable.fnDeleteRow( 0 );
		 *    } );
		 */
		this.fnDeleteRow = function( mTarget, fnCallBack, bRedraw )
		{
			/* Find settings from table node */
			var oSettings = _fnSettingsFromNode( this[DataTable.ext.iApiIndex] );
			var i, iLen, iAODataIndex;
			
			iAODataIndex = (typeof mTarget === 'object') ? 
				_fnNodeToDataIndex(oSettings, mTarget) : mTarget;
			
			/* Return the data array from this row */
			var oData = oSettings.aoData.splice( iAODataIndex, 1 );
		
			/* Update the _DT_RowIndex parameter */
			for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
			{
				if ( oSettings.aoData[i].nTr !== null )
				{
					oSettings.aoData[i].nTr._DT_RowIndex = i;
				}
			}
			
			/* Remove the target row from the search array */
			var iDisplayIndex = $.inArray( iAODataIndex, oSettings.aiDisplay );
			oSettings.asDataSearch.splice( iDisplayIndex, 1 );
			
			/* Delete from the display arrays */
			_fnDeleteIndex( oSettings.aiDisplayMaster, iAODataIndex );
			_fnDeleteIndex( oSettings.aiDisplay, iAODataIndex );
			
			/* If there is a user callback function - call it */
			if ( typeof fnCallBack === "function" )
			{
				fnCallBack.call( this, oSettings, oData );
			}
			
			/* Check for an 'overflow' they case for displaying the table */
			if ( oSettings._iDisplayStart >= oSettings.fnRecordsDisplay() )
			{
				oSettings._iDisplayStart -= oSettings._iDisplayLength;
				if ( oSettings._iDisplayStart < 0 )
				{
					oSettings._iDisplayStart = 0;
				}
			}
			
			if ( bRedraw === undefined || bRedraw )
			{
				_fnCalculateEnd( oSettings );
				_fnDraw( oSettings );
			}
			
			return oData;
		};
		
		
		/**
		 * Restore the table to it's original state in the DOM by removing all of DataTables 
		 * enhancements, alterations to the DOM structure of the table and event listeners.
		 *  @param {boolean} [bRemove=false] Completely remove the table from the DOM
		 *  @dtopt API
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      // This example is fairly pointless in reality, but shows how fnDestroy can be used
		 *      var oTable = $('#example').dataTable();
		 *      oTable.fnDestroy();
		 *    } );
		 */
		this.fnDestroy = function ( bRemove )
		{
			var oSettings = _fnSettingsFromNode( this[DataTable.ext.iApiIndex] );
			var nOrig = oSettings.nTableWrapper.parentNode;
			var nBody = oSettings.nTBody;
			var i, iLen;
		
			bRemove = (bRemove===undefined) ? false : bRemove;
			
			/* Flag to note that the table is currently being destroyed - no action should be taken */
			oSettings.bDestroying = true;
			
			/* Fire off the destroy callbacks for plug-ins etc */
			_fnCallbackFire( oSettings, "aoDestroyCallback", "destroy", [oSettings] );
		
			/* If the table is not being removed, restore the hidden columns */
			if ( !bRemove )
			{
				for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
				{
					if ( oSettings.aoColumns[i].bVisible === false )
					{
						this.fnSetColumnVis( i, true );
					}
				}
			}
			
			/* Blitz all DT events */
			$(oSettings.nTableWrapper).find('*').andSelf().unbind('.DT');
			
			/* If there is an 'empty' indicator row, remove it */
			$('tbody>tr>td.'+oSettings.oClasses.sRowEmpty, oSettings.nTable).parent().remove();
			
			/* When scrolling we had to break the table up - restore it */
			if ( oSettings.nTable != oSettings.nTHead.parentNode )
			{
				$(oSettings.nTable).children('thead').remove();
				oSettings.nTable.appendChild( oSettings.nTHead );
			}
			
			if ( oSettings.nTFoot && oSettings.nTable != oSettings.nTFoot.parentNode )
			{
				$(oSettings.nTable).children('tfoot').remove();
				oSettings.nTable.appendChild( oSettings.nTFoot );
			}
			
			/* Remove the DataTables generated nodes, events and classes */
			oSettings.nTable.parentNode.removeChild( oSettings.nTable );
			$(oSettings.nTableWrapper).remove();
			
			oSettings.aaSorting = [];
			oSettings.aaSortingFixed = [];
			_fnSortingClasses( oSettings );
			
			$(_fnGetTrNodes( oSettings )).removeClass( oSettings.asStripeClasses.join(' ') );
			
			$('th, td', oSettings.nTHead).removeClass( [
				oSettings.oClasses.sSortable,
				oSettings.oClasses.sSortableAsc,
				oSettings.oClasses.sSortableDesc,
				oSettings.oClasses.sSortableNone ].join(' ')
			);
			if ( oSettings.bJUI )
			{
				$('th span.'+oSettings.oClasses.sSortIcon
					+ ', td span.'+oSettings.oClasses.sSortIcon, oSettings.nTHead).remove();
		
				$('th, td', oSettings.nTHead).each( function () {
					var jqWrapper = $('div.'+oSettings.oClasses.sSortJUIWrapper, this);
					var kids = jqWrapper.contents();
					$(this).append( kids );
					jqWrapper.remove();
				} );
			}
			
			/* Add the TR elements back into the table in their original order */
			if ( !bRemove && oSettings.nTableReinsertBefore )
			{
				nOrig.insertBefore( oSettings.nTable, oSettings.nTableReinsertBefore );
			}
			else if ( !bRemove )
			{
				nOrig.appendChild( oSettings.nTable );
			}
		
			for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
			{
				if ( oSettings.aoData[i].nTr !== null )
				{
					nBody.appendChild( oSettings.aoData[i].nTr );
				}
			}
			
			/* Restore the width of the original table */
			if ( oSettings.oFeatures.bAutoWidth === true )
			{
			  oSettings.nTable.style.width = _fnStringToCss(oSettings.sDestroyWidth);
			}
			
			/* If the were originally stripe classes - then we add them back here. Note
			 * this is not fool proof (for example if not all rows had stripe classes - but
			 * it's a good effort without getting carried away
			 */
			iLen = oSettings.asDestroyStripes.length;
			if (iLen)
			{
				var anRows = $(nBody).children('tr');
				for ( i=0 ; i<iLen ; i++ )
				{
					anRows.filter(':nth-child(' + iLen + 'n + ' + i + ')').addClass( oSettings.asDestroyStripes[i] );
				}
			}
			
			/* Remove the settings object from the settings array */
			for ( i=0, iLen=DataTable.settings.length ; i<iLen ; i++ )
			{
				if ( DataTable.settings[i] == oSettings )
				{
					DataTable.settings.splice( i, 1 );
				}
			}
			
			/* End it all */
			oSettings = null;
			oInit = null;
		};
		
		
		/**
		 * Redraw the table
		 *  @param {bool} [bComplete=true] Re-filter and resort (if enabled) the table before the draw.
		 *  @dtopt API
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *      
		 *      // Re-draw the table - you wouldn't want to do it here, but it's an example :-)
		 *      oTable.fnDraw();
		 *    } );
		 */
		this.fnDraw = function( bComplete )
		{
			var oSettings = _fnSettingsFromNode( this[DataTable.ext.iApiIndex] );
			if ( bComplete === false )
			{
				_fnCalculateEnd( oSettings );
				_fnDraw( oSettings );
			}
			else
			{
				_fnReDraw( oSettings );
			}
		};
		
		
		/**
		 * Filter the input based on data
		 *  @param {string} sInput String to filter the table on
		 *  @param {int|null} [iColumn] Column to limit filtering to
		 *  @param {bool} [bRegex=false] Treat as regular expression or not
		 *  @param {bool} [bSmart=true] Perform smart filtering or not
		 *  @param {bool} [bShowGlobal=true] Show the input global filter in it's input box(es)
		 *  @param {bool} [bCaseInsensitive=true] Do case-insensitive matching (true) or not (false)
		 *  @dtopt API
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *      
		 *      // Sometime later - filter...
		 *      oTable.fnFilter( 'test string' );
		 *    } );
		 */
		this.fnFilter = function( sInput, iColumn, bRegex, bSmart, bShowGlobal, bCaseInsensitive )
		{
			var oSettings = _fnSettingsFromNode( this[DataTable.ext.iApiIndex] );
			
			if ( !oSettings.oFeatures.bFilter )
			{
				return;
			}
			
			if ( bRegex === undefined || bRegex === null )
			{
				bRegex = false;
			}
			
			if ( bSmart === undefined || bSmart === null )
			{
				bSmart = true;
			}
			
			if ( bShowGlobal === undefined || bShowGlobal === null )
			{
				bShowGlobal = true;
			}
			
			if ( bCaseInsensitive === undefined || bCaseInsensitive === null )
			{
				bCaseInsensitive = true;
			}
			
			if ( iColumn === undefined || iColumn === null )
			{
				/* Global filter */
				_fnFilterComplete( oSettings, {
					"sSearch":sInput+"",
					"bRegex": bRegex,
					"bSmart": bSmart,
					"bCaseInsensitive": bCaseInsensitive
				}, 1 );
				
				if ( bShowGlobal && oSettings.aanFeatures.f )
				{
					var n = oSettings.aanFeatures.f;
					for ( var i=0, iLen=n.length ; i<iLen ; i++ )
					{
						// IE9 throws an 'unknown error' if document.activeElement is used
						// inside an iframe or frame...
						try {
							if ( n[i]._DT_Input != document.activeElement )
							{
								$(n[i]._DT_Input).val( sInput );
							}
						}
						catch ( e ) {
							$(n[i]._DT_Input).val( sInput );
						}
					}
				}
			}
			else
			{
				/* Single column filter */
				$.extend( oSettings.aoPreSearchCols[ iColumn ], {
					"sSearch": sInput+"",
					"bRegex": bRegex,
					"bSmart": bSmart,
					"bCaseInsensitive": bCaseInsensitive
				} );
				_fnFilterComplete( oSettings, oSettings.oPreviousSearch, 1 );
			}
		};
		
		
		/**
		 * Get the data for the whole table, an individual row or an individual cell based on the 
		 * provided parameters.
		 *  @param {int|node} [mRow] A TR row node, TD/TH cell node or an integer. If given as
		 *    a TR node then the data source for the whole row will be returned. If given as a
		 *    TD/TH cell node then iCol will be automatically calculated and the data for the
		 *    cell returned. If given as an integer, then this is treated as the aoData internal
		 *    data index for the row (see fnGetPosition) and the data for that row used.
		 *  @param {int} [iCol] Optional column index that you want the data of.
		 *  @returns {array|object|string} If mRow is undefined, then the data for all rows is
		 *    returned. If mRow is defined, just data for that row, and is iCol is
		 *    defined, only data for the designated cell is returned.
		 *  @dtopt API
		 *
		 *  @example
		 *    // Row data
		 *    $(document).ready(function() {
		 *      oTable = $('#example').dataTable();
		 *
		 *      oTable.$('tr').click( function () {
		 *        var data = oTable.fnGetData( this );
		 *        // ... do something with the array / object of data for the row
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Individual cell data
		 *    $(document).ready(function() {
		 *      oTable = $('#example').dataTable();
		 *
		 *      oTable.$('td').click( function () {
		 *        var sData = oTable.fnGetData( this );
		 *        alert( 'The cell clicked on had the value of '+sData );
		 *      } );
		 *    } );
		 */
		this.fnGetData = function( mRow, iCol )
		{
			var oSettings = _fnSettingsFromNode( this[DataTable.ext.iApiIndex] );
			
			if ( mRow !== undefined )
			{
				var iRow = mRow;
				if ( typeof mRow === 'object' )
				{
					var sNode = mRow.nodeName.toLowerCase();
					if (sNode === "tr" )
					{
						iRow = _fnNodeToDataIndex(oSettings, mRow);
					}
					else if ( sNode === "td" )
					{
						iRow = _fnNodeToDataIndex(oSettings, mRow.parentNode);
						iCol = _fnNodeToColumnIndex( oSettings, iRow, mRow );
					}
				}
		
				if ( iCol !== undefined )
				{
					return _fnGetCellData( oSettings, iRow, iCol, '' );
				}
				return (oSettings.aoData[iRow]!==undefined) ?
					oSettings.aoData[iRow]._aData : null;
			}
			return _fnGetDataMaster( oSettings );
		};
		
		
		/**
		 * Get an array of the TR nodes that are used in the table's body. Note that you will 
		 * typically want to use the '$' API method in preference to this as it is more 
		 * flexible.
		 *  @param {int} [iRow] Optional row index for the TR element you want
		 *  @returns {array|node} If iRow is undefined, returns an array of all TR elements
		 *    in the table's body, or iRow is defined, just the TR element requested.
		 *  @dtopt API
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *      
		 *      // Get the nodes from the table
		 *      var nNodes = oTable.fnGetNodes( );
		 *    } );
		 */
		this.fnGetNodes = function( iRow )
		{
			var oSettings = _fnSettingsFromNode( this[DataTable.ext.iApiIndex] );
			
			if ( iRow !== undefined ) {
				return (oSettings.aoData[iRow]!==undefined) ?
					oSettings.aoData[iRow].nTr : null;
			}
			return _fnGetTrNodes( oSettings );
		};
		
		
		/**
		 * Get the array indexes of a particular cell from it's DOM element
		 * and column index including hidden columns
		 *  @param {node} nNode this can either be a TR, TD or TH in the table's body
		 *  @returns {int} If nNode is given as a TR, then a single index is returned, or
		 *    if given as a cell, an array of [row index, column index (visible), 
		 *    column index (all)] is given.
		 *  @dtopt API
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      $('#example tbody td').click( function () {
		 *        // Get the position of the current data from the node
		 *        var aPos = oTable.fnGetPosition( this );
		 *        
		 *        // Get the data array for this row
		 *        var aData = oTable.fnGetData( aPos[0] );
		 *        
		 *        // Update the data array and return the value
		 *        aData[ aPos[1] ] = 'clicked';
		 *        this.innerHTML = 'clicked';
		 *      } );
		 *      
		 *      // Init DataTables
		 *      oTable = $('#example').dataTable();
		 *    } );
		 */
		this.fnGetPosition = function( nNode )
		{
			var oSettings = _fnSettingsFromNode( this[DataTable.ext.iApiIndex] );
			var sNodeName = nNode.nodeName.toUpperCase();
			
			if ( sNodeName == "TR" )
			{
				return _fnNodeToDataIndex(oSettings, nNode);
			}
			else if ( sNodeName == "TD" || sNodeName == "TH" )
			{
				var iDataIndex = _fnNodeToDataIndex( oSettings, nNode.parentNode );
				var iColumnIndex = _fnNodeToColumnIndex( oSettings, iDataIndex, nNode );
				return [ iDataIndex, _fnColumnIndexToVisible(oSettings, iColumnIndex ), iColumnIndex ];
			}
			return null;
		};
		
		
		/**
		 * Check to see if a row is 'open' or not.
		 *  @param {node} nTr the table row to check
		 *  @returns {boolean} true if the row is currently open, false otherwise
		 *  @dtopt API
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable;
		 *      
		 *      // 'open' an information row when a row is clicked on
		 *      $('#example tbody tr').click( function () {
		 *        if ( oTable.fnIsOpen(this) ) {
		 *          oTable.fnClose( this );
		 *        } else {
		 *          oTable.fnOpen( this, "Temporary row opened", "info_row" );
		 *        }
		 *      } );
		 *      
		 *      oTable = $('#example').dataTable();
		 *    } );
		 */
		this.fnIsOpen = function( nTr )
		{
			var oSettings = _fnSettingsFromNode( this[DataTable.ext.iApiIndex] );
			var aoOpenRows = oSettings.aoOpenRows;
			
			for ( var i=0 ; i<oSettings.aoOpenRows.length ; i++ )
			{
				if ( oSettings.aoOpenRows[i].nParent == nTr )
				{
					return true;
				}
			}
			return false;
		};
		
		
		/**
		 * This function will place a new row directly after a row which is currently 
		 * on display on the page, with the HTML contents that is passed into the 
		 * function. This can be used, for example, to ask for confirmation that a 
		 * particular record should be deleted.
		 *  @param {node} nTr The table row to 'open'
		 *  @param {string|node|jQuery} mHtml The HTML to put into the row
		 *  @param {string} sClass Class to give the new TD cell
		 *  @returns {node} The row opened. Note that if the table row passed in as the
		 *    first parameter, is not found in the table, this method will silently
		 *    return.
		 *  @dtopt API
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable;
		 *      
		 *      // 'open' an information row when a row is clicked on
		 *      $('#example tbody tr').click( function () {
		 *        if ( oTable.fnIsOpen(this) ) {
		 *          oTable.fnClose( this );
		 *        } else {
		 *          oTable.fnOpen( this, "Temporary row opened", "info_row" );
		 *        }
		 *      } );
		 *      
		 *      oTable = $('#example').dataTable();
		 *    } );
		 */
		this.fnOpen = function( nTr, mHtml, sClass )
		{
			/* Find settings from table node */
			var oSettings = _fnSettingsFromNode( this[DataTable.ext.iApiIndex] );
		
			/* Check that the row given is in the table */
			var nTableRows = _fnGetTrNodes( oSettings );
			if ( $.inArray(nTr, nTableRows) === -1 )
			{
				return;
			}
			
			/* the old open one if there is one */
			this.fnClose( nTr );
			
			var nNewRow = document.createElement("tr");
			var nNewCell = document.createElement("td");
			nNewRow.appendChild( nNewCell );
			nNewCell.className = sClass;
			nNewCell.colSpan = _fnVisbleColumns( oSettings );
		
			if (typeof mHtml === "string")
			{
				nNewCell.innerHTML = mHtml;
			}
			else
			{
				$(nNewCell).html( mHtml );
			}
		
			/* If the nTr isn't on the page at the moment - then we don't insert at the moment */
			var nTrs = $('tr', oSettings.nTBody);
			if ( $.inArray(nTr, nTrs) != -1  )
			{
				$(nNewRow).insertAfter(nTr);
			}
			
			oSettings.aoOpenRows.push( {
				"nTr": nNewRow,
				"nParent": nTr
			} );
			
			return nNewRow;
		};
		
		
		/**
		 * Change the pagination - provides the internal logic for pagination in a simple API 
		 * function. With this function you can have a DataTables table go to the next, 
		 * previous, first or last pages.
		 *  @param {string|int} mAction Paging action to take: "first", "previous", "next" or "last"
		 *    or page number to jump to (integer), note that page 0 is the first page.
		 *  @param {bool} [bRedraw=true] Redraw the table or not
		 *  @dtopt API
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *      oTable.fnPageChange( 'next' );
		 *    } );
		 */
		this.fnPageChange = function ( mAction, bRedraw )
		{
			var oSettings = _fnSettingsFromNode( this[DataTable.ext.iApiIndex] );
			_fnPageChange( oSettings, mAction );
			_fnCalculateEnd( oSettings );
			
			if ( bRedraw === undefined || bRedraw )
			{
				_fnDraw( oSettings );
			}
		};
		
		
		/**
		 * Show a particular column
		 *  @param {int} iCol The column whose display should be changed
		 *  @param {bool} bShow Show (true) or hide (false) the column
		 *  @param {bool} [bRedraw=true] Redraw the table or not
		 *  @dtopt API
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *      
		 *      // Hide the second column after initialisation
		 *      oTable.fnSetColumnVis( 1, false );
		 *    } );
		 */
		this.fnSetColumnVis = function ( iCol, bShow, bRedraw )
		{
			var oSettings = _fnSettingsFromNode( this[DataTable.ext.iApiIndex] );
			var i, iLen;
			var aoColumns = oSettings.aoColumns;
			var aoData = oSettings.aoData;
			var nTd, bAppend, iBefore;
			
			/* No point in doing anything if we are requesting what is already true */
			if ( aoColumns[iCol].bVisible == bShow )
			{
				return;
			}
			
			/* Show the column */
			if ( bShow )
			{
				var iInsert = 0;
				for ( i=0 ; i<iCol ; i++ )
				{
					if ( aoColumns[i].bVisible )
					{
						iInsert++;
					}
				}
				
				/* Need to decide if we should use appendChild or insertBefore */
				bAppend = (iInsert >= _fnVisbleColumns( oSettings ));
		
				/* Which coloumn should we be inserting before? */
				if ( !bAppend )
				{
					for ( i=iCol ; i<aoColumns.length ; i++ )
					{
						if ( aoColumns[i].bVisible )
						{
							iBefore = i;
							break;
						}
					}
				}
		
				for ( i=0, iLen=aoData.length ; i<iLen ; i++ )
				{
					if ( aoData[i].nTr !== null )
					{
						if ( bAppend )
						{
							aoData[i].nTr.appendChild( 
								aoData[i]._anHidden[iCol]
							);
						}
						else
						{
							aoData[i].nTr.insertBefore(
								aoData[i]._anHidden[iCol], 
								_fnGetTdNodes( oSettings, i )[iBefore] );
						}
					}
				}
			}
			else
			{
				/* Remove a column from display */
				for ( i=0, iLen=aoData.length ; i<iLen ; i++ )
				{
					if ( aoData[i].nTr !== null )
					{
						nTd = _fnGetTdNodes( oSettings, i )[iCol];
						aoData[i]._anHidden[iCol] = nTd;
						nTd.parentNode.removeChild( nTd );
					}
				}
			}
		
			/* Clear to set the visible flag */
			aoColumns[iCol].bVisible = bShow;
		
			/* Redraw the header and footer based on the new column visibility */
			_fnDrawHead( oSettings, oSettings.aoHeader );
			if ( oSettings.nTFoot )
			{
				_fnDrawHead( oSettings, oSettings.aoFooter );
			}
			
			/* If there are any 'open' rows, then we need to alter the colspan for this col change */
			for ( i=0, iLen=oSettings.aoOpenRows.length ; i<iLen ; i++ )
			{
				oSettings.aoOpenRows[i].nTr.colSpan = _fnVisbleColumns( oSettings );
			}
			
			/* Do a redraw incase anything depending on the table columns needs it 
			 * (built-in: scrolling) 
			 */
			if ( bRedraw === undefined || bRedraw )
			{
				_fnAdjustColumnSizing( oSettings );
				_fnDraw( oSettings );
			}
			
			_fnSaveState( oSettings );
		};
		
		
		/**
		 * Get the settings for a particular table for external manipulation
		 *  @returns {object} DataTables settings object. See 
		 *    {@link DataTable.models.oSettings}
		 *  @dtopt API
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *      var oSettings = oTable.fnSettings();
		 *      
		 *      // Show an example parameter from the settings
		 *      alert( oSettings._iDisplayStart );
		 *    } );
		 */
		this.fnSettings = function()
		{
			return _fnSettingsFromNode( this[DataTable.ext.iApiIndex] );
		};
		
		
		/**
		 * Sort the table by a particular column
		 *  @param {int} iCol the data index to sort on. Note that this will not match the 
		 *    'display index' if you have hidden data entries
		 *  @dtopt API
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *      
		 *      // Sort immediately with columns 0 and 1
		 *      oTable.fnSort( [ [0,'asc'], [1,'asc'] ] );
		 *    } );
		 */
		this.fnSort = function( aaSort )
		{
			var oSettings = _fnSettingsFromNode( this[DataTable.ext.iApiIndex] );
			oSettings.aaSorting = aaSort;
			_fnSort( oSettings );
		};
		
		
		/**
		 * Attach a sort listener to an element for a given column
		 *  @param {node} nNode the element to attach the sort listener to
		 *  @param {int} iColumn the column that a click on this node will sort on
		 *  @param {function} [fnCallback] callback function when sort is run
		 *  @dtopt API
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *      
		 *      // Sort on column 1, when 'sorter' is clicked on
		 *      oTable.fnSortListener( document.getElementById('sorter'), 1 );
		 *    } );
		 */
		this.fnSortListener = function( nNode, iColumn, fnCallback )
		{
			_fnSortAttachListener( _fnSettingsFromNode( this[DataTable.ext.iApiIndex] ), nNode, iColumn,
			 	fnCallback );
		};
		
		
		/**
		 * Update a table cell or row - this method will accept either a single value to
		 * update the cell with, an array of values with one element for each column or
		 * an object in the same format as the original data source. The function is
		 * self-referencing in order to make the multi column updates easier.
		 *  @param {object|array|string} mData Data to update the cell/row with
		 *  @param {node|int} mRow TR element you want to update or the aoData index
		 *  @param {int} [iColumn] The column to update (not used of mData is an array or object)
		 *  @param {bool} [bRedraw=true] Redraw the table or not
		 *  @param {bool} [bAction=true] Perform pre-draw actions or not
		 *  @returns {int} 0 on success, 1 on error
		 *  @dtopt API
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *      oTable.fnUpdate( 'Example update', 0, 0 ); // Single cell
		 *      oTable.fnUpdate( ['a', 'b', 'c', 'd', 'e'], 1, 0 ); // Row
		 *    } );
		 */
		this.fnUpdate = function( mData, mRow, iColumn, bRedraw, bAction )
		{
			var oSettings = _fnSettingsFromNode( this[DataTable.ext.iApiIndex] );
			var i, iLen, sDisplay;
			var iRow = (typeof mRow === 'object') ? 
				_fnNodeToDataIndex(oSettings, mRow) : mRow;
			
			if ( $.isArray(mData) && iColumn === undefined )
			{
				/* Array update - update the whole row */
				oSettings.aoData[iRow]._aData = mData.slice();
				
				/* Flag to the function that we are recursing */
				for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
				{
					this.fnUpdate( _fnGetCellData( oSettings, iRow, i ), iRow, i, false, false );
				}
			}
			else if ( $.isPlainObject(mData) && iColumn === undefined )
			{
				/* Object update - update the whole row - assume the developer gets the object right */
				oSettings.aoData[iRow]._aData = $.extend( true, {}, mData );
		
				for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
				{
					this.fnUpdate( _fnGetCellData( oSettings, iRow, i ), iRow, i, false, false );
				}
			}
			else
			{
				/* Individual cell update */
				_fnSetCellData( oSettings, iRow, iColumn, mData );
				sDisplay = _fnGetCellData( oSettings, iRow, iColumn, 'display' );
				
				var oCol = oSettings.aoColumns[iColumn];
				if ( oCol.fnRender !== null )
				{
					sDisplay = _fnRender( oSettings, iRow, iColumn );
					if ( oCol.bUseRendered )
					{
						_fnSetCellData( oSettings, iRow, iColumn, sDisplay );
					}
				}
				
				if ( oSettings.aoData[iRow].nTr !== null )
				{
					/* Do the actual HTML update */
					_fnGetTdNodes( oSettings, iRow )[iColumn].innerHTML = sDisplay;
				}
			}
			
			/* Modify the search index for this row (strictly this is likely not needed, since fnReDraw
			 * will rebuild the search array - however, the redraw might be disabled by the user)
			 */
			var iDisplayIndex = $.inArray( iRow, oSettings.aiDisplay );
			oSettings.asDataSearch[iDisplayIndex] = _fnBuildSearchRow(
				oSettings, 
				_fnGetRowData( oSettings, iRow, 'filter', _fnGetColumns( oSettings, 'bSearchable' ) )
			);
			
			/* Perform pre-draw actions */
			if ( bAction === undefined || bAction )
			{
				_fnAdjustColumnSizing( oSettings );
			}
			
			/* Redraw the table */
			if ( bRedraw === undefined || bRedraw )
			{
				_fnReDraw( oSettings );
			}
			return 0;
		};
		
		
		/**
		 * Provide a common method for plug-ins to check the version of DataTables being used, in order
		 * to ensure compatibility.
		 *  @param {string} sVersion Version string to check for, in the format "X.Y.Z". Note that the
		 *    formats "X" and "X.Y" are also acceptable.
		 *  @returns {boolean} true if this version of DataTables is greater or equal to the required
		 *    version, or false if this version of DataTales is not suitable
		 *  @method
		 *  @dtopt API
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *      alert( oTable.fnVersionCheck( '1.9.0' ) );
		 *    } );
		 */
		this.fnVersionCheck = DataTable.ext.fnVersionCheck;
		
		
		/*
		 * This is really a good bit rubbish this method of exposing the internal methods
		 * publicly... - To be fixed in 2.0 using methods on the prototype
		 */
		
		
		/**
		 * Create a wrapper function for exporting an internal functions to an external API.
		 *  @param {string} sFunc API function name
		 *  @returns {function} wrapped function
		 *  @memberof DataTable#oApi
		 */
		function _fnExternApiFunc (sFunc)
		{
			return function() {
				var aArgs = [_fnSettingsFromNode(this[DataTable.ext.iApiIndex])].concat( 
					Array.prototype.slice.call(arguments) );
				return DataTable.ext.oApi[sFunc].apply( this, aArgs );
			};
		}
		
		
		/**
		 * Reference to internal functions for use by plug-in developers. Note that these
		 * methods are references to internal functions and are considered to be private.
		 * If you use these methods, be aware that they are liable to change between versions
		 * (check the upgrade notes).
		 *  @namespace
		 */
		this.oApi = {
			"_fnExternApiFunc": _fnExternApiFunc,
			"_fnInitialise": _fnInitialise,
			"_fnInitComplete": _fnInitComplete,
			"_fnLanguageCompat": _fnLanguageCompat,
			"_fnAddColumn": _fnAddColumn,
			"_fnColumnOptions": _fnColumnOptions,
			"_fnAddData": _fnAddData,
			"_fnCreateTr": _fnCreateTr,
			"_fnGatherData": _fnGatherData,
			"_fnBuildHead": _fnBuildHead,
			"_fnDrawHead": _fnDrawHead,
			"_fnDraw": _fnDraw,
			"_fnReDraw": _fnReDraw,
			"_fnAjaxUpdate": _fnAjaxUpdate,
			"_fnAjaxParameters": _fnAjaxParameters,
			"_fnAjaxUpdateDraw": _fnAjaxUpdateDraw,
			"_fnServerParams": _fnServerParams,
			"_fnAddOptionsHtml": _fnAddOptionsHtml,
			"_fnFeatureHtmlTable": _fnFeatureHtmlTable,
			"_fnScrollDraw": _fnScrollDraw,
			"_fnAdjustColumnSizing": _fnAdjustColumnSizing,
			"_fnFeatureHtmlFilter": _fnFeatureHtmlFilter,
			"_fnFilterComplete": _fnFilterComplete,
			"_fnFilterCustom": _fnFilterCustom,
			"_fnFilterColumn": _fnFilterColumn,
			"_fnFilter": _fnFilter,
			"_fnBuildSearchArray": _fnBuildSearchArray,
			"_fnBuildSearchRow": _fnBuildSearchRow,
			"_fnFilterCreateSearch": _fnFilterCreateSearch,
			"_fnDataToSearch": _fnDataToSearch,
			"_fnSort": _fnSort,
			"_fnSortAttachListener": _fnSortAttachListener,
			"_fnSortingClasses": _fnSortingClasses,
			"_fnFeatureHtmlPaginate": _fnFeatureHtmlPaginate,
			"_fnPageChange": _fnPageChange,
			"_fnFeatureHtmlInfo": _fnFeatureHtmlInfo,
			"_fnUpdateInfo": _fnUpdateInfo,
			"_fnFeatureHtmlLength": _fnFeatureHtmlLength,
			"_fnFeatureHtmlProcessing": _fnFeatureHtmlProcessing,
			"_fnProcessingDisplay": _fnProcessingDisplay,
			"_fnVisibleToColumnIndex": _fnVisibleToColumnIndex,
			"_fnColumnIndexToVisible": _fnColumnIndexToVisible,
			"_fnNodeToDataIndex": _fnNodeToDataIndex,
			"_fnVisbleColumns": _fnVisbleColumns,
			"_fnCalculateEnd": _fnCalculateEnd,
			"_fnConvertToWidth": _fnConvertToWidth,
			"_fnCalculateColumnWidths": _fnCalculateColumnWidths,
			"_fnScrollingWidthAdjust": _fnScrollingWidthAdjust,
			"_fnGetWidestNode": _fnGetWidestNode,
			"_fnGetMaxLenString": _fnGetMaxLenString,
			"_fnStringToCss": _fnStringToCss,
			"_fnDetectType": _fnDetectType,
			"_fnSettingsFromNode": _fnSettingsFromNode,
			"_fnGetDataMaster": _fnGetDataMaster,
			"_fnGetTrNodes": _fnGetTrNodes,
			"_fnGetTdNodes": _fnGetTdNodes,
			"_fnEscapeRegex": _fnEscapeRegex,
			"_fnDeleteIndex": _fnDeleteIndex,
			"_fnReOrderIndex": _fnReOrderIndex,
			"_fnColumnOrdering": _fnColumnOrdering,
			"_fnLog": _fnLog,
			"_fnClearTable": _fnClearTable,
			"_fnSaveState": _fnSaveState,
			"_fnLoadState": _fnLoadState,
			"_fnCreateCookie": _fnCreateCookie,
			"_fnReadCookie": _fnReadCookie,
			"_fnDetectHeader": _fnDetectHeader,
			"_fnGetUniqueThs": _fnGetUniqueThs,
			"_fnScrollBarWidth": _fnScrollBarWidth,
			"_fnApplyToChildren": _fnApplyToChildren,
			"_fnMap": _fnMap,
			"_fnGetRowData": _fnGetRowData,
			"_fnGetCellData": _fnGetCellData,
			"_fnSetCellData": _fnSetCellData,
			"_fnGetObjectDataFn": _fnGetObjectDataFn,
			"_fnSetObjectDataFn": _fnSetObjectDataFn,
			"_fnApplyColumnDefs": _fnApplyColumnDefs,
			"_fnBindAction": _fnBindAction,
			"_fnExtend": _fnExtend,
			"_fnCallbackReg": _fnCallbackReg,
			"_fnCallbackFire": _fnCallbackFire,
			"_fnJsonString": _fnJsonString,
			"_fnRender": _fnRender,
			"_fnNodeToColumnIndex": _fnNodeToColumnIndex,
			"_fnInfoMacros": _fnInfoMacros,
			"_fnBrowserDetect": _fnBrowserDetect,
			"_fnGetColumns": _fnGetColumns
		};
		
		$.extend( DataTable.ext.oApi, this.oApi );
		
		for ( var sFunc in DataTable.ext.oApi )
		{
			if ( sFunc )
			{
				this[sFunc] = _fnExternApiFunc(sFunc);
			}
		}
		
		
		var _that = this;
		this.each(function() {
			var i=0, iLen, j, jLen, k, kLen;
			var sId = this.getAttribute( 'id' );
			var bInitHandedOff = false;
			var bUsePassedData = false;
			
			
			/* Sanity check */
			if ( this.nodeName.toLowerCase() != 'table' )
			{
				_fnLog( null, 0, "Attempted to initialise DataTables on a node which is not a "+
					"table: "+this.nodeName );
				return;
			}
			
			/* Check to see if we are re-initialising a table */
			for ( i=0, iLen=DataTable.settings.length ; i<iLen ; i++ )
			{
				/* Base check on table node */
				if ( DataTable.settings[i].nTable == this )
				{
					if ( oInit === undefined || oInit.bRetrieve )
					{
						return DataTable.settings[i].oInstance;
					}
					else if ( oInit.bDestroy )
					{
						DataTable.settings[i].oInstance.fnDestroy();
						break;
					}
					else
					{
						_fnLog( DataTable.settings[i], 0, "Cannot reinitialise DataTable.\n\n"+
							"To retrieve the DataTables object for this table, pass no arguments or see "+
							"the docs for bRetrieve and bDestroy" );
						return;
					}
				}
				
				/* If the element we are initialising has the same ID as a table which was previously
				 * initialised, but the table nodes don't match (from before) then we destroy the old
				 * instance by simply deleting it. This is under the assumption that the table has been
				 * destroyed by other methods. Anyone using non-id selectors will need to do this manually
				 */
				if ( DataTable.settings[i].sTableId == this.id )
				{
					DataTable.settings.splice( i, 1 );
					break;
				}
			}
			
			/* Ensure the table has an ID - required for accessibility */
			if ( sId === null || sId === "" )
			{
				sId = "DataTables_Table_"+(DataTable.ext._oExternConfig.iNextUnique++);
				this.id = sId;
			}
			
			/* Create the settings object for this table and set some of the default parameters */
			var oSettings = $.extend( true, {}, DataTable.models.oSettings, {
				"nTable":        this,
				"oApi":          _that.oApi,
				"oInit":         oInit,
				"sDestroyWidth": $(this).width(),
				"sInstance":     sId,
				"sTableId":      sId
			} );
			DataTable.settings.push( oSettings );
			
			// Need to add the instance after the instance after the settings object has been added
			// to the settings array, so we can self reference the table instance if more than one
			oSettings.oInstance = (_that.length===1) ? _that : $(this).dataTable();
			
			/* Setting up the initialisation object */
			if ( !oInit )
			{
				oInit = {};
			}
			
			// Backwards compatibility, before we apply all the defaults
			if ( oInit.oLanguage )
			{
				_fnLanguageCompat( oInit.oLanguage );
			}
			
			oInit = _fnExtend( $.extend(true, {}, DataTable.defaults), oInit );
			
			// Map the initialisation options onto the settings object
			_fnMap( oSettings.oFeatures, oInit, "bPaginate" );
			_fnMap( oSettings.oFeatures, oInit, "bLengthChange" );
			_fnMap( oSettings.oFeatures, oInit, "bFilter" );
			_fnMap( oSettings.oFeatures, oInit, "bSort" );
			_fnMap( oSettings.oFeatures, oInit, "bInfo" );
			_fnMap( oSettings.oFeatures, oInit, "bProcessing" );
			_fnMap( oSettings.oFeatures, oInit, "bAutoWidth" );
			_fnMap( oSettings.oFeatures, oInit, "bSortClasses" );
			_fnMap( oSettings.oFeatures, oInit, "bServerSide" );
			_fnMap( oSettings.oFeatures, oInit, "bDeferRender" );
			_fnMap( oSettings.oScroll, oInit, "sScrollX", "sX" );
			_fnMap( oSettings.oScroll, oInit, "sScrollXInner", "sXInner" );
			_fnMap( oSettings.oScroll, oInit, "sScrollY", "sY" );
			_fnMap( oSettings.oScroll, oInit, "bScrollCollapse", "bCollapse" );
			_fnMap( oSettings.oScroll, oInit, "bScrollInfinite", "bInfinite" );
			_fnMap( oSettings.oScroll, oInit, "iScrollLoadGap", "iLoadGap" );
			_fnMap( oSettings.oScroll, oInit, "bScrollAutoCss", "bAutoCss" );
			_fnMap( oSettings, oInit, "asStripeClasses" );
			_fnMap( oSettings, oInit, "asStripClasses", "asStripeClasses" ); // legacy
			_fnMap( oSettings, oInit, "fnServerData" );
			_fnMap( oSettings, oInit, "fnFormatNumber" );
			_fnMap( oSettings, oInit, "sServerMethod" );
			_fnMap( oSettings, oInit, "aaSorting" );
			_fnMap( oSettings, oInit, "aaSortingFixed" );
			_fnMap( oSettings, oInit, "aLengthMenu" );
			_fnMap( oSettings, oInit, "sPaginationType" );
			_fnMap( oSettings, oInit, "sAjaxSource" );
			_fnMap( oSettings, oInit, "sAjaxDataProp" );
			_fnMap( oSettings, oInit, "iCookieDuration" );
			_fnMap( oSettings, oInit, "sCookiePrefix" );
			_fnMap( oSettings, oInit, "sDom" );
			_fnMap( oSettings, oInit, "bSortCellsTop" );
			_fnMap( oSettings, oInit, "iTabIndex" );
			_fnMap( oSettings, oInit, "oSearch", "oPreviousSearch" );
			_fnMap( oSettings, oInit, "aoSearchCols", "aoPreSearchCols" );
			_fnMap( oSettings, oInit, "iDisplayLength", "_iDisplayLength" );
			_fnMap( oSettings, oInit, "bJQueryUI", "bJUI" );
			_fnMap( oSettings, oInit, "fnCookieCallback" );
			_fnMap( oSettings, oInit, "fnStateLoad" );
			_fnMap( oSettings, oInit, "fnStateSave" );
			_fnMap( oSettings.oLanguage, oInit, "fnInfoCallback" );
			
			/* Callback functions which are array driven */
			_fnCallbackReg( oSettings, 'aoDrawCallback',       oInit.fnDrawCallback,      'user' );
			_fnCallbackReg( oSettings, 'aoServerParams',       oInit.fnServerParams,      'user' );
			_fnCallbackReg( oSettings, 'aoStateSaveParams',    oInit.fnStateSaveParams,   'user' );
			_fnCallbackReg( oSettings, 'aoStateLoadParams',    oInit.fnStateLoadParams,   'user' );
			_fnCallbackReg( oSettings, 'aoStateLoaded',        oInit.fnStateLoaded,       'user' );
			_fnCallbackReg( oSettings, 'aoRowCallback',        oInit.fnRowCallback,       'user' );
			_fnCallbackReg( oSettings, 'aoRowCreatedCallback', oInit.fnCreatedRow,        'user' );
			_fnCallbackReg( oSettings, 'aoHeaderCallback',     oInit.fnHeaderCallback,    'user' );
			_fnCallbackReg( oSettings, 'aoFooterCallback',     oInit.fnFooterCallback,    'user' );
			_fnCallbackReg( oSettings, 'aoInitComplete',       oInit.fnInitComplete,      'user' );
			_fnCallbackReg( oSettings, 'aoPreDrawCallback',    oInit.fnPreDrawCallback,   'user' );
			
			if ( oSettings.oFeatures.bServerSide && oSettings.oFeatures.bSort &&
				   oSettings.oFeatures.bSortClasses )
			{
				/* Enable sort classes for server-side processing. Safe to do it here, since server-side
				 * processing must be enabled by the developer
				 */
				_fnCallbackReg( oSettings, 'aoDrawCallback', _fnSortingClasses, 'server_side_sort_classes' );
			}
			else if ( oSettings.oFeatures.bDeferRender )
			{
				_fnCallbackReg( oSettings, 'aoDrawCallback', _fnSortingClasses, 'defer_sort_classes' );
			}
			
			if ( oInit.bJQueryUI )
			{
				/* Use the JUI classes object for display. You could clone the oStdClasses object if 
				 * you want to have multiple tables with multiple independent classes 
				 */
				$.extend( oSettings.oClasses, DataTable.ext.oJUIClasses );
				
				if ( oInit.sDom === DataTable.defaults.sDom && DataTable.defaults.sDom === "lfrtip" )
				{
					/* Set the DOM to use a layout suitable for jQuery UI's theming */
					oSettings.sDom = '<"H"lfr>t<"F"ip>';
				}
			}
			else
			{
				$.extend( oSettings.oClasses, DataTable.ext.oStdClasses );
			}
			$(this).addClass( oSettings.oClasses.sTable );
			
			/* Calculate the scroll bar width and cache it for use later on */
			if ( oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "" )
			{
				oSettings.oScroll.iBarWidth = _fnScrollBarWidth();
			}
			
			if ( oSettings.iInitDisplayStart === undefined )
			{
				/* Display start point, taking into account the save saving */
				oSettings.iInitDisplayStart = oInit.iDisplayStart;
				oSettings._iDisplayStart = oInit.iDisplayStart;
			}
			
			/* Must be done after everything which can be overridden by a cookie! */
			if ( oInit.bStateSave )
			{
				oSettings.oFeatures.bStateSave = true;
				_fnLoadState( oSettings, oInit );
				_fnCallbackReg( oSettings, 'aoDrawCallback', _fnSaveState, 'state_save' );
			}
			
			if ( oInit.iDeferLoading !== null )
			{
				oSettings.bDeferLoading = true;
				var tmp = $.isArray( oInit.iDeferLoading );
				oSettings._iRecordsDisplay = tmp ? oInit.iDeferLoading[0] : oInit.iDeferLoading;
				oSettings._iRecordsTotal = tmp ? oInit.iDeferLoading[1] : oInit.iDeferLoading;
			}
			
			if ( oInit.aaData !== null )
			{
				bUsePassedData = true;
			}
			
			/* Language definitions */
			if ( oInit.oLanguage.sUrl !== "" )
			{
				/* Get the language definitions from a file - because this Ajax call makes the language
				 * get async to the remainder of this function we use bInitHandedOff to indicate that 
				 * _fnInitialise will be fired by the returned Ajax handler, rather than the constructor
				 */
				oSettings.oLanguage.sUrl = oInit.oLanguage.sUrl;
				$.getJSON( oSettings.oLanguage.sUrl, null, function( json ) {
					_fnLanguageCompat( json );
					$.extend( true, oSettings.oLanguage, oInit.oLanguage, json );
					_fnInitialise( oSettings );
				} );
				bInitHandedOff = true;
			}
			else
			{
				$.extend( true, oSettings.oLanguage, oInit.oLanguage );
			}
			
			
			/*
			 * Stripes
			 */
			if ( oInit.asStripeClasses === null )
			{
				oSettings.asStripeClasses =[
					oSettings.oClasses.sStripeOdd,
					oSettings.oClasses.sStripeEven
				];
			}
			
			/* Remove row stripe classes if they are already on the table row */
			iLen=oSettings.asStripeClasses.length;
			oSettings.asDestroyStripes = [];
			if (iLen)
			{
				var bStripeRemove = false;
				var anRows = $(this).children('tbody').children('tr:lt(' + iLen + ')');
				for ( i=0 ; i<iLen ; i++ )
				{
					if ( anRows.hasClass( oSettings.asStripeClasses[i] ) )
					{
						bStripeRemove = true;
						
						/* Store the classes which we are about to remove so they can be re-added on destroy */
						oSettings.asDestroyStripes.push( oSettings.asStripeClasses[i] );
					}
				}
				
				if ( bStripeRemove )
				{
					anRows.removeClass( oSettings.asStripeClasses.join(' ') );
				}
			}
			
			/*
			 * Columns
			 * See if we should load columns automatically or use defined ones
			 */
			var anThs = [];
			var aoColumnsInit;
			var nThead = this.getElementsByTagName('thead');
			if ( nThead.length !== 0 )
			{
				_fnDetectHeader( oSettings.aoHeader, nThead[0] );
				anThs = _fnGetUniqueThs( oSettings );
			}
			
			/* If not given a column array, generate one with nulls */
			if ( oInit.aoColumns === null )
			{
				aoColumnsInit = [];
				for ( i=0, iLen=anThs.length ; i<iLen ; i++ )
				{
					aoColumnsInit.push( null );
				}
			}
			else
			{
				aoColumnsInit = oInit.aoColumns;
			}
			
			/* Add the columns */
			for ( i=0, iLen=aoColumnsInit.length ; i<iLen ; i++ )
			{
				/* Short cut - use the loop to check if we have column visibility state to restore */
				if ( oInit.saved_aoColumns !== undefined && oInit.saved_aoColumns.length == iLen )
				{
					if ( aoColumnsInit[i] === null )
					{
						aoColumnsInit[i] = {};
					}
					aoColumnsInit[i].bVisible = oInit.saved_aoColumns[i].bVisible;
				}
				
				_fnAddColumn( oSettings, anThs ? anThs[i] : null );
			}
			
			/* Apply the column definitions */
			_fnApplyColumnDefs( oSettings, oInit.aoColumnDefs, aoColumnsInit, function (iCol, oDef) {
				_fnColumnOptions( oSettings, iCol, oDef );
			} );
			
			
			/*
			 * Sorting
			 * Check the aaSorting array
			 */
			for ( i=0, iLen=oSettings.aaSorting.length ; i<iLen ; i++ )
			{
				if ( oSettings.aaSorting[i][0] >= oSettings.aoColumns.length )
				{
					oSettings.aaSorting[i][0] = 0;
				}
				var oColumn = oSettings.aoColumns[ oSettings.aaSorting[i][0] ];
				
				/* Add a default sorting index */
				if ( oSettings.aaSorting[i][2] === undefined )
				{
					oSettings.aaSorting[i][2] = 0;
				}
				
				/* If aaSorting is not defined, then we use the first indicator in asSorting */
				if ( oInit.aaSorting === undefined && oSettings.saved_aaSorting === undefined )
				{
					oSettings.aaSorting[i][1] = oColumn.asSorting[0];
				}
				
				/* Set the current sorting index based on aoColumns.asSorting */
				for ( j=0, jLen=oColumn.asSorting.length ; j<jLen ; j++ )
				{
					if ( oSettings.aaSorting[i][1] == oColumn.asSorting[j] )
					{
						oSettings.aaSorting[i][2] = j;
						break;
					}
				}
			}
				
			/* Do a first pass on the sorting classes (allows any size changes to be taken into
			 * account, and also will apply sorting disabled classes if disabled
			 */
			_fnSortingClasses( oSettings );
			
			
			/*
			 * Final init
			 * Cache the header, body and footer as required, creating them if needed
			 */
			
			/* Browser support detection */
			_fnBrowserDetect( oSettings );
			
			// Work around for Webkit bug 83867 - store the caption-side before removing from doc
			var captions = $(this).children('caption').each( function () {
				this._captionSide = $(this).css('caption-side');
			} );
			
			var thead = $(this).children('thead');
			if ( thead.length === 0 )
			{
				thead = [ document.createElement( 'thead' ) ];
				this.appendChild( thead[0] );
			}
			oSettings.nTHead = thead[0];
			
			var tbody = $(this).children('tbody');
			if ( tbody.length === 0 )
			{
				tbody = [ document.createElement( 'tbody' ) ];
				this.appendChild( tbody[0] );
			}
			oSettings.nTBody = tbody[0];
			oSettings.nTBody.setAttribute( "role", "alert" );
			oSettings.nTBody.setAttribute( "aria-live", "polite" );
			oSettings.nTBody.setAttribute( "aria-relevant", "all" );
			
			var tfoot = $(this).children('tfoot');
			if ( tfoot.length === 0 && captions.length > 0 && (oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "") )
			{
				// If we are a scrolling table, and no footer has been given, then we need to create
				// a tfoot element for the caption element to be appended to
				tfoot = [ document.createElement( 'tfoot' ) ];
				this.appendChild( tfoot[0] );
			}
			
			if ( tfoot.length > 0 )
			{
				oSettings.nTFoot = tfoot[0];
				_fnDetectHeader( oSettings.aoFooter, oSettings.nTFoot );
			}
			
			/* Check if there is data passing into the constructor */
			if ( bUsePassedData )
			{
				for ( i=0 ; i<oInit.aaData.length ; i++ )
				{
					_fnAddData( oSettings, oInit.aaData[ i ] );
				}
			}
			else
			{
				/* Grab the data from the page */
				_fnGatherData( oSettings );
			}
			
			/* Copy the data index array */
			oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
			
			/* Initialisation complete - table can be drawn */
			oSettings.bInitialised = true;
			
			/* Check if we need to initialise the table (it might not have been handed off to the
			 * language processor)
			 */
			if ( bInitHandedOff === false )
			{
				_fnInitialise( oSettings );
			}
		} );
		_that = null;
		return this;
	};

	
	
	/**
	 * Provide a common method for plug-ins to check the version of DataTables being used, in order
	 * to ensure compatibility.
	 *  @param {string} sVersion Version string to check for, in the format "X.Y.Z". Note that the
	 *    formats "X" and "X.Y" are also acceptable.
	 *  @returns {boolean} true if this version of DataTables is greater or equal to the required
	 *    version, or false if this version of DataTales is not suitable
	 *  @static
	 *  @dtopt API-Static
	 *
	 *  @example
	 *    alert( $.fn.dataTable.fnVersionCheck( '1.9.0' ) );
	 */
	DataTable.fnVersionCheck = function( sVersion )
	{
		/* This is cheap, but effective */
		var fnZPad = function (Zpad, count)
		{
			while(Zpad.length < count) {
				Zpad += '0';
			}
			return Zpad;
		};
		var aThis = DataTable.ext.sVersion.split('.');
		var aThat = sVersion.split('.');
		var sThis = '', sThat = '';
		
		for ( var i=0, iLen=aThat.length ; i<iLen ; i++ )
		{
			sThis += fnZPad( aThis[i], 3 );
			sThat += fnZPad( aThat[i], 3 );
		}
		
		return parseInt(sThis, 10) >= parseInt(sThat, 10);
	};
	
	
	/**
	 * Check if a TABLE node is a DataTable table already or not.
	 *  @param {node} nTable The TABLE node to check if it is a DataTable or not (note that other
	 *    node types can be passed in, but will always return false).
	 *  @returns {boolean} true the table given is a DataTable, or false otherwise
	 *  @static
	 *  @dtopt API-Static
	 *
	 *  @example
	 *    var ex = document.getElementById('example');
	 *    if ( ! $.fn.DataTable.fnIsDataTable( ex ) ) {
	 *      $(ex).dataTable();
	 *    }
	 */
	DataTable.fnIsDataTable = function ( nTable )
	{
		var o = DataTable.settings;
	
		for ( var i=0 ; i<o.length ; i++ )
		{
			if ( o[i].nTable === nTable || o[i].nScrollHead === nTable || o[i].nScrollFoot === nTable )
			{
				return true;
			}
		}
	
		return false;
	};
	
	
	/**
	 * Get all DataTable tables that have been initialised - optionally you can select to
	 * get only currently visible tables.
	 *  @param {boolean} [bVisible=false] Flag to indicate if you want all (default) or 
	 *    visible tables only.
	 *  @returns {array} Array of TABLE nodes (not DataTable instances) which are DataTables
	 *  @static
	 *  @dtopt API-Static
	 *
	 *  @example
	 *    var table = $.fn.dataTable.fnTables(true);
	 *    if ( table.length > 0 ) {
	 *      $(table).dataTable().fnAdjustColumnSizing();
	 *    }
	 */
	DataTable.fnTables = function ( bVisible )
	{
		var out = [];
	
		jQuery.each( DataTable.settings, function (i, o) {
			if ( !bVisible || (bVisible === true && $(o.nTable).is(':visible')) )
			{
				out.push( o.nTable );
			}
		} );
	
		return out;
	};
	

	/**
	 * Version string for plug-ins to check compatibility. Allowed format is
	 * a.b.c.d.e where: a:int, b:int, c:int, d:string(dev|beta), e:int. d and
	 * e are optional
	 *  @member
	 *  @type string
	 *  @default Version number
	 */
	DataTable.version = "1.9.4";

	/**
	 * Private data store, containing all of the settings objects that are created for the
	 * tables on a given page.
	 * 
	 * Note that the <i>DataTable.settings</i> object is aliased to <i>jQuery.fn.dataTableExt</i> 
	 * through which it may be accessed and manipulated, or <i>jQuery.fn.dataTable.settings</i>.
	 *  @member
	 *  @type array
	 *  @default []
	 *  @private
	 */
	DataTable.settings = [];

	/**
	 * Object models container, for the various models that DataTables has available
	 * to it. These models define the objects that are used to hold the active state 
	 * and configuration of the table.
	 *  @namespace
	 */
	DataTable.models = {};
	
	
	/**
	 * DataTables extension options and plug-ins. This namespace acts as a collection "area"
	 * for plug-ins that can be used to extend the default DataTables behaviour - indeed many
	 * of the build in methods use this method to provide their own capabilities (sorting methods
	 * for example).
	 * 
	 * Note that this namespace is aliased to jQuery.fn.dataTableExt so it can be readily accessed
	 * and modified by plug-ins.
	 *  @namespace
	 */
	DataTable.models.ext = {
		/**
		 * Plug-in filtering functions - this method of filtering is complimentary to the default
		 * type based filtering, and a lot more comprehensive as it allows you complete control
		 * over the filtering logic. Each element in this array is a function (parameters
		 * described below) that is called for every row in the table, and your logic decides if
		 * it should be included in the filtered data set or not.
		 *   <ul>
		 *     <li>
		 *       Function input parameters:
		 *       <ul>
		 *         <li>{object} DataTables settings object: see {@link DataTable.models.oSettings}.</li>
		 *         <li>{array|object} Data for the row to be processed (same as the original format
		 *           that was passed in as the data source, or an array from a DOM data source</li>
		 *         <li>{int} Row index in aoData ({@link DataTable.models.oSettings.aoData}), which can
		 *           be useful to retrieve the TR element if you need DOM interaction.</li>
		 *       </ul>
		 *     </li>
		 *     <li>
		 *       Function return:
		 *       <ul>
		 *         <li>{boolean} Include the row in the filtered result set (true) or not (false)</li>
		 *       </ul>
		 *     </il>
		 *   </ul>
		 *  @type array
		 *  @default []
		 *
		 *  @example
		 *    // The following example shows custom filtering being applied to the fourth column (i.e.
		 *    // the aData[3] index) based on two input values from the end-user, matching the data in 
		 *    // a certain range.
		 *    $.fn.dataTableExt.afnFiltering.push(
		 *      function( oSettings, aData, iDataIndex ) {
		 *        var iMin = document.getElementById('min').value * 1;
		 *        var iMax = document.getElementById('max').value * 1;
		 *        var iVersion = aData[3] == "-" ? 0 : aData[3]*1;
		 *        if ( iMin == "" && iMax == "" ) {
		 *          return true;
		 *        }
		 *        else if ( iMin == "" && iVersion < iMax ) {
		 *          return true;
		 *        }
		 *        else if ( iMin < iVersion && "" == iMax ) {
		 *          return true;
		 *        }
		 *        else if ( iMin < iVersion && iVersion < iMax ) {
		 *          return true;
		 *        }
		 *        return false;
		 *      }
		 *    );
		 */
		"afnFiltering": [],
	
	
		/**
		 * Plug-in sorting functions - this method of sorting is complimentary to the default type
		 * based sorting that DataTables does automatically, allowing much greater control over the
		 * the data that is being used to sort a column. This is useful if you want to do sorting
		 * based on live data (for example the contents of an 'input' element) rather than just the
		 * static string that DataTables knows of. The way these plug-ins work is that you create
		 * an array of the values you wish to be sorted for the column in question and then return
		 * that array. Which pre-sorting function is run here depends on the sSortDataType parameter
		 * that is used for the column (if any). This is the corollary of <i>ofnSearch</i> for sort 
		 * data.
		 *   <ul>
	     *     <li>
	     *       Function input parameters:
	     *       <ul>
		 *         <li>{object} DataTables settings object: see {@link DataTable.models.oSettings}.</li>
	     *         <li>{int} Target column index</li>
	     *       </ul>
	     *     </li>
		 *     <li>
		 *       Function return:
		 *       <ul>
		 *         <li>{array} Data for the column to be sorted upon</li>
		 *       </ul>
		 *     </il>
		 *   </ul>
		 *  
		 * Note that as of v1.9, it is typically preferable to use <i>mData</i> to prepare data for
		 * the different uses that DataTables can put the data to. Specifically <i>mData</i> when
		 * used as a function will give you a 'type' (sorting, filtering etc) that you can use to 
		 * prepare the data as required for the different types. As such, this method is deprecated.
		 *  @type array
		 *  @default []
		 *  @deprecated
		 *
		 *  @example
		 *    // Updating the cached sorting information with user entered values in HTML input elements
		 *    jQuery.fn.dataTableExt.afnSortData['dom-text'] = function ( oSettings, iColumn )
		 *    {
		 *      var aData = [];
		 *      $( 'td:eq('+iColumn+') input', oSettings.oApi._fnGetTrNodes(oSettings) ).each( function () {
		 *        aData.push( this.value );
		 *      } );
		 *      return aData;
		 *    }
		 */
		"afnSortData": [],
	
	
		/**
		 * Feature plug-ins - This is an array of objects which describe the feature plug-ins that are
		 * available to DataTables. These feature plug-ins are accessible through the sDom initialisation
		 * option. As such, each feature plug-in must describe a function that is used to initialise
		 * itself (fnInit), a character so the feature can be enabled by sDom (cFeature) and the name
		 * of the feature (sFeature). Thus the objects attached to this method must provide:
		 *   <ul>
		 *     <li>{function} fnInit Initialisation of the plug-in
		 *       <ul>
	     *         <li>
	     *           Function input parameters:
	     *           <ul>
		 *             <li>{object} DataTables settings object: see {@link DataTable.models.oSettings}.</li>
	     *           </ul>
	     *         </li>
		 *         <li>
		 *           Function return:
		 *           <ul>
		 *             <li>{node|null} The element which contains your feature. Note that the return
		 *                may also be void if your plug-in does not require to inject any DOM elements 
		 *                into DataTables control (sDom) - for example this might be useful when 
		 *                developing a plug-in which allows table control via keyboard entry.</li>
		 *           </ul>
		 *         </il>
		 *       </ul>
		 *     </li>
		 *     <li>{character} cFeature Character that will be matched in sDom - case sensitive</li>
		 *     <li>{string} sFeature Feature name</li>
		 *   </ul>
		 *  @type array
		 *  @default []
		 * 
		 *  @example
		 *    // How TableTools initialises itself.
		 *    $.fn.dataTableExt.aoFeatures.push( {
		 *      "fnInit": function( oSettings ) {
		 *        return new TableTools( { "oDTSettings": oSettings } );
		 *      },
		 *      "cFeature": "T",
		 *      "sFeature": "TableTools"
		 *    } );
		 */
		"aoFeatures": [],
	
	
		/**
		 * Type detection plug-in functions - DataTables utilises types to define how sorting and
		 * filtering behave, and types can be either  be defined by the developer (sType for the
		 * column) or they can be automatically detected by the methods in this array. The functions
		 * defined in the array are quite simple, taking a single parameter (the data to analyse) 
		 * and returning the type if it is a known type, or null otherwise.
		 *   <ul>
	     *     <li>
	     *       Function input parameters:
	     *       <ul>
		 *         <li>{*} Data from the column cell to be analysed</li>
	     *       </ul>
	     *     </li>
		 *     <li>
		 *       Function return:
		 *       <ul>
		 *         <li>{string|null} Data type detected, or null if unknown (and thus pass it
		 *           on to the other type detection functions.</li>
		 *       </ul>
		 *     </il>
		 *   </ul>
		 *  @type array
		 *  @default []
		 *  
		 *  @example
		 *    // Currency type detection plug-in:
		 *    jQuery.fn.dataTableExt.aTypes.push(
		 *      function ( sData ) {
		 *        var sValidChars = "0123456789.-";
		 *        var Char;
		 *        
		 *        // Check the numeric part
		 *        for ( i=1 ; i<sData.length ; i++ ) {
		 *          Char = sData.charAt(i); 
		 *          if (sValidChars.indexOf(Char) == -1) {
		 *            return null;
		 *          }
		 *        }
		 *        
		 *        // Check prefixed by currency
		 *        if ( sData.charAt(0) == '$' || sData.charAt(0) == '&pound;' ) {
		 *          return 'currency';
		 *        }
		 *        return null;
		 *      }
		 *    );
		 */
		"aTypes": [],
	
	
		/**
		 * Provide a common method for plug-ins to check the version of DataTables being used, 
		 * in order to ensure compatibility.
		 *  @type function
		 *  @param {string} sVersion Version string to check for, in the format "X.Y.Z". Note 
		 *    that the formats "X" and "X.Y" are also acceptable.
		 *  @returns {boolean} true if this version of DataTables is greater or equal to the 
		 *    required version, or false if this version of DataTales is not suitable
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *      alert( oTable.fnVersionCheck( '1.9.0' ) );
		 *    } );
		 */
		"fnVersionCheck": DataTable.fnVersionCheck,
	
	
		/**
		 * Index for what 'this' index API functions should use
		 *  @type int
		 *  @default 0
		 */
		"iApiIndex": 0,
	
	
		/**
		 * Pre-processing of filtering data plug-ins - When you assign the sType for a column
		 * (or have it automatically detected for you by DataTables or a type detection plug-in), 
		 * you will typically be using this for custom sorting, but it can also be used to provide 
		 * custom filtering by allowing you to pre-processing the data and returning the data in
		 * the format that should be filtered upon. This is done by adding functions this object 
		 * with a parameter name which matches the sType for that target column. This is the
		 * corollary of <i>afnSortData</i> for filtering data.
		 *   <ul>
	     *     <li>
	     *       Function input parameters:
	     *       <ul>
		 *         <li>{*} Data from the column cell to be prepared for filtering</li>
	     *       </ul>
	     *     </li>
		 *     <li>
		 *       Function return:
		 *       <ul>
		 *         <li>{string|null} Formatted string that will be used for the filtering.</li>
		 *       </ul>
		 *     </il>
		 *   </ul>
		 * 
		 * Note that as of v1.9, it is typically preferable to use <i>mData</i> to prepare data for
		 * the different uses that DataTables can put the data to. Specifically <i>mData</i> when
		 * used as a function will give you a 'type' (sorting, filtering etc) that you can use to 
		 * prepare the data as required for the different types. As such, this method is deprecated.
		 *  @type object
		 *  @default {}
		 *  @deprecated
		 *
		 *  @example
		 *    $.fn.dataTableExt.ofnSearch['title-numeric'] = function ( sData ) {
		 *      return sData.replace(/\n/g," ").replace( /<.*?>/g, "" );
		 *    }
		 */
		"ofnSearch": {},
	
	
		/**
		 * Container for all private functions in DataTables so they can be exposed externally
		 *  @type object
		 *  @default {}
		 */
		"oApi": {},
	
	
		/**
		 * Storage for the various classes that DataTables uses
		 *  @type object
		 *  @default {}
		 */
		"oStdClasses": {},
		
	
		/**
		 * Storage for the various classes that DataTables uses - jQuery UI suitable
		 *  @type object
		 *  @default {}
		 */
		"oJUIClasses": {},
	
	
		/**
		 * Pagination plug-in methods - The style and controls of the pagination can significantly 
		 * impact on how the end user interacts with the data in your table, and DataTables allows 
		 * the addition of pagination controls by extending this object, which can then be enabled
		 * through the <i>sPaginationType</i> initialisation parameter. Each pagination type that
		 * is added is an object (the property name of which is what <i>sPaginationType</i> refers
		 * to) that has two properties, both methods that are used by DataTables to update the
		 * control's state.
		 *   <ul>
		 *     <li>
		 *       fnInit -  Initialisation of the paging controls. Called only during initialisation 
		 *         of the table. It is expected that this function will add the required DOM elements 
		 *         to the page for the paging controls to work. The element pointer 
		 *         'oSettings.aanFeatures.p' array is provided by DataTables to contain the paging 
		 *         controls (note that this is a 2D array to allow for multiple instances of each 
		 *         DataTables DOM element). It is suggested that you add the controls to this element 
		 *         as children
		 *       <ul>
	     *         <li>
	     *           Function input parameters:
	     *           <ul>
		 *             <li>{object} DataTables settings object: see {@link DataTable.models.oSettings}.</li>
		 *             <li>{node} Container into which the pagination controls must be inserted</li>
		 *             <li>{function} Draw callback function - whenever the controls cause a page
		 *               change, this method must be called to redraw the table.</li>
	     *           </ul>
	     *         </li>
		 *         <li>
		 *           Function return:
		 *           <ul>
		 *             <li>No return required</li>
		 *           </ul>
		 *         </il>
		 *       </ul>
		 *     </il>
		 *     <li>
		 *       fnInit -  This function is called whenever the paging status of the table changes and is
		 *         typically used to update classes and/or text of the paging controls to reflex the new 
		 *         status.
		 *       <ul>
	     *         <li>
	     *           Function input parameters:
	     *           <ul>
		 *             <li>{object} DataTables settings object: see {@link DataTable.models.oSettings}.</li>
		 *             <li>{function} Draw callback function - in case you need to redraw the table again
		 *               or attach new event listeners</li>
	     *           </ul>
	     *         </li>
		 *         <li>
		 *           Function return:
		 *           <ul>
		 *             <li>No return required</li>
		 *           </ul>
		 *         </il>
		 *       </ul>
		 *     </il>
		 *   </ul>
		 *  @type object
		 *  @default {}
		 *
		 *  @example
		 *    $.fn.dataTableExt.oPagination.four_button = {
		 *      "fnInit": function ( oSettings, nPaging, fnCallbackDraw ) {
		 *        nFirst = document.createElement( 'span' );
		 *        nPrevious = document.createElement( 'span' );
		 *        nNext = document.createElement( 'span' );
		 *        nLast = document.createElement( 'span' );
		 *        
		 *        nFirst.appendChild( document.createTextNode( oSettings.oLanguage.oPaginate.sFirst ) );
		 *        nPrevious.appendChild( document.createTextNode( oSettings.oLanguage.oPaginate.sPrevious ) );
		 *        nNext.appendChild( document.createTextNode( oSettings.oLanguage.oPaginate.sNext ) );
		 *        nLast.appendChild( document.createTextNode( oSettings.oLanguage.oPaginate.sLast ) );
		 *        
		 *        nFirst.className = "paginate_button first";
		 *        nPrevious.className = "paginate_button previous";
		 *        nNext.className="paginate_button next";
		 *        nLast.className = "paginate_button last";
		 *        
		 *        nPaging.appendChild( nFirst );
		 *        nPaging.appendChild( nPrevious );
		 *        nPaging.appendChild( nNext );
		 *        nPaging.appendChild( nLast );
		 *        
		 *        $(nFirst).click( function () {
		 *          oSettings.oApi._fnPageChange( oSettings, "first" );
		 *          fnCallbackDraw( oSettings );
		 *        } );
		 *        
		 *        $(nPrevious).click( function() {
		 *          oSettings.oApi._fnPageChange( oSettings, "previous" );
		 *          fnCallbackDraw( oSettings );
		 *        } );
		 *        
		 *        $(nNext).click( function() {
		 *          oSettings.oApi._fnPageChange( oSettings, "next" );
		 *          fnCallbackDraw( oSettings );
		 *        } );
		 *        
		 *        $(nLast).click( function() {
		 *          oSettings.oApi._fnPageChange( oSettings, "last" );
		 *          fnCallbackDraw( oSettings );
		 *        } );
		 *        
		 *        $(nFirst).bind( 'selectstart', function () { return false; } );
		 *        $(nPrevious).bind( 'selectstart', function () { return false; } );
		 *        $(nNext).bind( 'selectstart', function () { return false; } );
		 *        $(nLast).bind( 'selectstart', function () { return false; } );
		 *      },
		 *      
		 *      "fnUpdate": function ( oSettings, fnCallbackDraw ) {
		 *        if ( !oSettings.aanFeatures.p ) {
		 *          return;
		 *        }
		 *        
		 *        // Loop over each instance of the pager
		 *        var an = oSettings.aanFeatures.p;
		 *        for ( var i=0, iLen=an.length ; i<iLen ; i++ ) {
		 *          var buttons = an[i].getElementsByTagName('span');
		 *          if ( oSettings._iDisplayStart === 0 ) {
		 *            buttons[0].className = "paginate_disabled_previous";
		 *            buttons[1].className = "paginate_disabled_previous";
		 *          }
		 *          else {
		 *            buttons[0].className = "paginate_enabled_previous";
		 *            buttons[1].className = "paginate_enabled_previous";
		 *          }
		 *          
		 *          if ( oSettings.fnDisplayEnd() == oSettings.fnRecordsDisplay() ) {
		 *            buttons[2].className = "paginate_disabled_next";
		 *            buttons[3].className = "paginate_disabled_next";
		 *          }
		 *          else {
		 *            buttons[2].className = "paginate_enabled_next";
		 *            buttons[3].className = "paginate_enabled_next";
		 *          }
		 *        }
		 *      }
		 *    };
		 */
		"oPagination": {},
	
	
		/**
		 * Sorting plug-in methods - Sorting in DataTables is based on the detected type of the
		 * data column (you can add your own type detection functions, or override automatic 
		 * detection using sType). With this specific type given to the column, DataTables will 
		 * apply the required sort from the functions in the object. Each sort type must provide
		 * two mandatory methods, one each for ascending and descending sorting, and can optionally
		 * provide a pre-formatting method that will help speed up sorting by allowing DataTables
		 * to pre-format the sort data only once (rather than every time the actual sort functions
		 * are run). The two sorting functions are typical Javascript sort methods:
		 *   <ul>
	     *     <li>
	     *       Function input parameters:
	     *       <ul>
		 *         <li>{*} Data to compare to the second parameter</li>
		 *         <li>{*} Data to compare to the first parameter</li>
	     *       </ul>
	     *     </li>
		 *     <li>
		 *       Function return:
		 *       <ul>
		 *         <li>{int} Sorting match: <0 if first parameter should be sorted lower than
		 *           the second parameter, ===0 if the two parameters are equal and >0 if
		 *           the first parameter should be sorted height than the second parameter.</li>
		 *       </ul>
		 *     </il>
		 *   </ul>
		 *  @type object
		 *  @default {}
		 *
		 *  @example
		 *    // Case-sensitive string sorting, with no pre-formatting method
		 *    $.extend( $.fn.dataTableExt.oSort, {
		 *      "string-case-asc": function(x,y) {
		 *        return ((x < y) ? -1 : ((x > y) ? 1 : 0));
		 *      },
		 *      "string-case-desc": function(x,y) {
		 *        return ((x < y) ? 1 : ((x > y) ? -1 : 0));
		 *      }
		 *    } );
		 *
		 *  @example
		 *    // Case-insensitive string sorting, with pre-formatting
		 *    $.extend( $.fn.dataTableExt.oSort, {
		 *      "string-pre": function(x) {
		 *        return x.toLowerCase();
		 *      },
		 *      "string-asc": function(x,y) {
		 *        return ((x < y) ? -1 : ((x > y) ? 1 : 0));
		 *      },
		 *      "string-desc": function(x,y) {
		 *        return ((x < y) ? 1 : ((x > y) ? -1 : 0));
		 *      }
		 *    } );
		 */
		"oSort": {},
	
	
		/**
		 * Version string for plug-ins to check compatibility. Allowed format is
		 * a.b.c.d.e where: a:int, b:int, c:int, d:string(dev|beta), e:int. d and
		 * e are optional
		 *  @type string
		 *  @default Version number
		 */
		"sVersion": DataTable.version,
	
	
		/**
		 * How should DataTables report an error. Can take the value 'alert' or 'throw'
		 *  @type string
		 *  @default alert
		 */
		"sErrMode": "alert",
	
	
		/**
		 * Store information for DataTables to access globally about other instances
		 *  @namespace
		 *  @private
		 */
		"_oExternConfig": {
			/* int:iNextUnique - next unique number for an instance */
			"iNextUnique": 0
		}
	};
	
	
	
	
	/**
	 * Template object for the way in which DataTables holds information about
	 * search information for the global filter and individual column filters.
	 *  @namespace
	 */
	DataTable.models.oSearch = {
		/**
		 * Flag to indicate if the filtering should be case insensitive or not
		 *  @type boolean
		 *  @default true
		 */
		"bCaseInsensitive": true,
	
		/**
		 * Applied search term
		 *  @type string
		 *  @default <i>Empty string</i>
		 */
		"sSearch": "",
	
		/**
		 * Flag to indicate if the search term should be interpreted as a
		 * regular expression (true) or not (false) and therefore and special
		 * regex characters escaped.
		 *  @type boolean
		 *  @default false
		 */
		"bRegex": false,
	
		/**
		 * Flag to indicate if DataTables is to use its smart filtering or not.
		 *  @type boolean
		 *  @default true
		 */
		"bSmart": true
	};
	
	
	
	
	/**
	 * Template object for the way in which DataTables holds information about
	 * each individual row. This is the object format used for the settings 
	 * aoData array.
	 *  @namespace
	 */
	DataTable.models.oRow = {
		/**
		 * TR element for the row
		 *  @type node
		 *  @default null
		 */
		"nTr": null,
	
		/**
		 * Data object from the original data source for the row. This is either
		 * an array if using the traditional form of DataTables, or an object if
		 * using mData options. The exact type will depend on the passed in
		 * data from the data source, or will be an array if using DOM a data 
		 * source.
		 *  @type array|object
		 *  @default []
		 */
		"_aData": [],
	
		/**
		 * Sorting data cache - this array is ostensibly the same length as the
		 * number of columns (although each index is generated only as it is 
		 * needed), and holds the data that is used for sorting each column in the
		 * row. We do this cache generation at the start of the sort in order that
		 * the formatting of the sort data need be done only once for each cell
		 * per sort. This array should not be read from or written to by anything
		 * other than the master sorting methods.
		 *  @type array
		 *  @default []
		 *  @private
		 */
		"_aSortData": [],
	
		/**
		 * Array of TD elements that are cached for hidden rows, so they can be
		 * reinserted into the table if a column is made visible again (or to act
		 * as a store if a column is made hidden). Only hidden columns have a 
		 * reference in the array. For non-hidden columns the value is either
		 * undefined or null.
		 *  @type array nodes
		 *  @default []
		 *  @private
		 */
		"_anHidden": [],
	
		/**
		 * Cache of the class name that DataTables has applied to the row, so we
		 * can quickly look at this variable rather than needing to do a DOM check
		 * on className for the nTr property.
		 *  @type string
		 *  @default <i>Empty string</i>
		 *  @private
		 */
		"_sRowStripe": ""
	};
	
	
	
	/**
	 * Template object for the column information object in DataTables. This object
	 * is held in the settings aoColumns array and contains all the information that
	 * DataTables needs about each individual column.
	 * 
	 * Note that this object is related to {@link DataTable.defaults.columns} 
	 * but this one is the internal data store for DataTables's cache of columns.
	 * It should NOT be manipulated outside of DataTables. Any configuration should
	 * be done through the initialisation options.
	 *  @namespace
	 */
	DataTable.models.oColumn = {
		/**
		 * A list of the columns that sorting should occur on when this column
		 * is sorted. That this property is an array allows multi-column sorting
		 * to be defined for a column (for example first name / last name columns
		 * would benefit from this). The values are integers pointing to the
		 * columns to be sorted on (typically it will be a single integer pointing
		 * at itself, but that doesn't need to be the case).
		 *  @type array
		 */
		"aDataSort": null,
	
		/**
		 * Define the sorting directions that are applied to the column, in sequence
		 * as the column is repeatedly sorted upon - i.e. the first value is used
		 * as the sorting direction when the column if first sorted (clicked on).
		 * Sort it again (click again) and it will move on to the next index.
		 * Repeat until loop.
		 *  @type array
		 */
		"asSorting": null,
		
		/**
		 * Flag to indicate if the column is searchable, and thus should be included
		 * in the filtering or not.
		 *  @type boolean
		 */
		"bSearchable": null,
		
		/**
		 * Flag to indicate if the column is sortable or not.
		 *  @type boolean
		 */
		"bSortable": null,
		
		/**
		 * <code>Deprecated</code> When using fnRender, you have two options for what 
		 * to do with the data, and this property serves as the switch. Firstly, you 
		 * can have the sorting and filtering use the rendered value (true - default), 
		 * or you can have the sorting and filtering us the original value (false).
		 *
		 * Please note that this option has now been deprecated and will be removed
		 * in the next version of DataTables. Please use mRender / mData rather than
		 * fnRender.
		 *  @type boolean
		 *  @deprecated
		 */
		"bUseRendered": null,
		
		/**
		 * Flag to indicate if the column is currently visible in the table or not
		 *  @type boolean
		 */
		"bVisible": null,
		
		/**
		 * Flag to indicate to the type detection method if the automatic type
		 * detection should be used, or if a column type (sType) has been specified
		 *  @type boolean
		 *  @default true
		 *  @private
		 */
		"_bAutoType": true,
		
		/**
		 * Developer definable function that is called whenever a cell is created (Ajax source,
		 * etc) or processed for input (DOM source). This can be used as a compliment to mRender
		 * allowing you to modify the DOM element (add background colour for example) when the
		 * element is available.
		 *  @type function
		 *  @param {element} nTd The TD node that has been created
		 *  @param {*} sData The Data for the cell
		 *  @param {array|object} oData The data for the whole row
		 *  @param {int} iRow The row index for the aoData data store
		 *  @default null
		 */
		"fnCreatedCell": null,
		
		/**
		 * Function to get data from a cell in a column. You should <b>never</b>
		 * access data directly through _aData internally in DataTables - always use
		 * the method attached to this property. It allows mData to function as
		 * required. This function is automatically assigned by the column 
		 * initialisation method
		 *  @type function
		 *  @param {array|object} oData The data array/object for the array 
		 *    (i.e. aoData[]._aData)
		 *  @param {string} sSpecific The specific data type you want to get - 
		 *    'display', 'type' 'filter' 'sort'
		 *  @returns {*} The data for the cell from the given row's data
		 *  @default null
		 */
		"fnGetData": null,
		
		/**
		 * <code>Deprecated</code> Custom display function that will be called for the 
		 * display of each cell in this column.
		 *
		 * Please note that this option has now been deprecated and will be removed
		 * in the next version of DataTables. Please use mRender / mData rather than
		 * fnRender.
		 *  @type function
		 *  @param {object} o Object with the following parameters:
		 *  @param {int}    o.iDataRow The row in aoData
		 *  @param {int}    o.iDataColumn The column in question
		 *  @param {array}  o.aData The data for the row in question
		 *  @param {object} o.oSettings The settings object for this DataTables instance
		 *  @returns {string} The string you which to use in the display
		 *  @default null
		 *  @deprecated
		 */
		"fnRender": null,
		
		/**
		 * Function to set data for a cell in the column. You should <b>never</b> 
		 * set the data directly to _aData internally in DataTables - always use
		 * this method. It allows mData to function as required. This function
		 * is automatically assigned by the column initialisation method
		 *  @type function
		 *  @param {array|object} oData The data array/object for the array 
		 *    (i.e. aoData[]._aData)
		 *  @param {*} sValue Value to set
		 *  @default null
		 */
		"fnSetData": null,
		
		/**
		 * Property to read the value for the cells in the column from the data 
		 * source array / object. If null, then the default content is used, if a
		 * function is given then the return from the function is used.
		 *  @type function|int|string|null
		 *  @default null
		 */
		"mData": null,
		
		/**
		 * Partner property to mData which is used (only when defined) to get
		 * the data - i.e. it is basically the same as mData, but without the
		 * 'set' option, and also the data fed to it is the result from mData.
		 * This is the rendering method to match the data method of mData.
		 *  @type function|int|string|null
		 *  @default null
		 */
		"mRender": null,
		
		/**
		 * Unique header TH/TD element for this column - this is what the sorting
		 * listener is attached to (if sorting is enabled.)
		 *  @type node
		 *  @default null
		 */
		"nTh": null,
		
		/**
		 * Unique footer TH/TD element for this column (if there is one). Not used 
		 * in DataTables as such, but can be used for plug-ins to reference the 
		 * footer for each column.
		 *  @type node
		 *  @default null
		 */
		"nTf": null,
		
		/**
		 * The class to apply to all TD elements in the table's TBODY for the column
		 *  @type string
		 *  @default null
		 */
		"sClass": null,
		
		/**
		 * When DataTables calculates the column widths to assign to each column,
		 * it finds the longest string in each column and then constructs a
		 * temporary table and reads the widths from that. The problem with this
		 * is that "mmm" is much wider then "iiii", but the latter is a longer 
		 * string - thus the calculation can go wrong (doing it properly and putting
		 * it into an DOM object and measuring that is horribly(!) slow). Thus as
		 * a "work around" we provide this option. It will append its value to the
		 * text that is found to be the longest string for the column - i.e. padding.
		 *  @type string
		 */
		"sContentPadding": null,
		
		/**
		 * Allows a default value to be given for a column's data, and will be used
		 * whenever a null data source is encountered (this can be because mData
		 * is set to null, or because the data source itself is null).
		 *  @type string
		 *  @default null
		 */
		"sDefaultContent": null,
		
		/**
		 * Name for the column, allowing reference to the column by name as well as
		 * by index (needs a lookup to work by name).
		 *  @type string
		 */
		"sName": null,
		
		/**
		 * Custom sorting data type - defines which of the available plug-ins in
		 * afnSortData the custom sorting will use - if any is defined.
		 *  @type string
		 *  @default std
		 */
		"sSortDataType": 'std',
		
		/**
		 * Class to be applied to the header element when sorting on this column
		 *  @type string
		 *  @default null
		 */
		"sSortingClass": null,
		
		/**
		 * Class to be applied to the header element when sorting on this column -
		 * when jQuery UI theming is used.
		 *  @type string
		 *  @default null
		 */
		"sSortingClassJUI": null,
		
		/**
		 * Title of the column - what is seen in the TH element (nTh).
		 *  @type string
		 */
		"sTitle": null,
		
		/**
		 * Column sorting and filtering type
		 *  @type string
		 *  @default null
		 */
		"sType": null,
		
		/**
		 * Width of the column
		 *  @type string
		 *  @default null
		 */
		"sWidth": null,
		
		/**
		 * Width of the column when it was first "encountered"
		 *  @type string
		 *  @default null
		 */
		"sWidthOrig": null
	};
	
	
	
	/**
	 * Initialisation options that can be given to DataTables at initialisation 
	 * time.
	 *  @namespace
	 */
	DataTable.defaults = {
		/**
		 * An array of data to use for the table, passed in at initialisation which 
		 * will be used in preference to any data which is already in the DOM. This is
		 * particularly useful for constructing tables purely in Javascript, for
		 * example with a custom Ajax call.
		 *  @type array
		 *  @default null
		 *  @dtopt Option
		 * 
		 *  @example
		 *    // Using a 2D array data source
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "aaData": [
		 *          ['Trident', 'Internet Explorer 4.0', 'Win 95+', 4, 'X'],
		 *          ['Trident', 'Internet Explorer 5.0', 'Win 95+', 5, 'C'],
		 *        ],
		 *        "aoColumns": [
		 *          { "sTitle": "Engine" },
		 *          { "sTitle": "Browser" },
		 *          { "sTitle": "Platform" },
		 *          { "sTitle": "Version" },
		 *          { "sTitle": "Grade" }
		 *        ]
		 *      } );
		 *    } );
		 *    
		 *  @example
		 *    // Using an array of objects as a data source (mData)
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "aaData": [
		 *          {
		 *            "engine":   "Trident",
		 *            "browser":  "Internet Explorer 4.0",
		 *            "platform": "Win 95+",
		 *            "version":  4,
		 *            "grade":    "X"
		 *          },
		 *          {
		 *            "engine":   "Trident",
		 *            "browser":  "Internet Explorer 5.0",
		 *            "platform": "Win 95+",
		 *            "version":  5,
		 *            "grade":    "C"
		 *          }
		 *        ],
		 *        "aoColumns": [
		 *          { "sTitle": "Engine",   "mData": "engine" },
		 *          { "sTitle": "Browser",  "mData": "browser" },
		 *          { "sTitle": "Platform", "mData": "platform" },
		 *          { "sTitle": "Version",  "mData": "version" },
		 *          { "sTitle": "Grade",    "mData": "grade" }
		 *        ]
		 *      } );
		 *    } );
		 */
		"aaData": null,
	
	
		/**
		 * If sorting is enabled, then DataTables will perform a first pass sort on 
		 * initialisation. You can define which column(s) the sort is performed upon, 
		 * and the sorting direction, with this variable. The aaSorting array should 
		 * contain an array for each column to be sorted initially containing the 
		 * column's index and a direction string ('asc' or 'desc').
		 *  @type array
		 *  @default [[0,'asc']]
		 *  @dtopt Option
		 * 
		 *  @example
		 *    // Sort by 3rd column first, and then 4th column
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aaSorting": [[2,'asc'], [3,'desc']]
		 *      } );
		 *    } );
		 *    
		 *    // No initial sorting
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aaSorting": []
		 *      } );
		 *    } );
		 */
		"aaSorting": [[0,'asc']],
	
	
		/**
		 * This parameter is basically identical to the aaSorting parameter, but 
		 * cannot be overridden by user interaction with the table. What this means 
		 * is that you could have a column (visible or hidden) which the sorting will 
		 * always be forced on first - any sorting after that (from the user) will 
		 * then be performed as required. This can be useful for grouping rows 
		 * together.
		 *  @type array
		 *  @default null
		 *  @dtopt Option
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aaSortingFixed": [[0,'asc']]
		 *      } );
		 *    } )
		 */
		"aaSortingFixed": null,
	
	
		/**
		 * This parameter allows you to readily specify the entries in the length drop
		 * down menu that DataTables shows when pagination is enabled. It can be 
		 * either a 1D array of options which will be used for both the displayed 
		 * option and the value, or a 2D array which will use the array in the first 
		 * position as the value, and the array in the second position as the 
		 * displayed options (useful for language strings such as 'All').
		 *  @type array
		 *  @default [ 10, 25, 50, 100 ]
		 *  @dtopt Option
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aLengthMenu": [[10, 25, 50, -1], [10, 25, 50, "All"]]
		 *      } );
		 *    } );
		 *  
		 *  @example
		 *    // Setting the default display length as well as length menu
		 *    // This is likely to be wanted if you remove the '10' option which
		 *    // is the iDisplayLength default.
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "iDisplayLength": 25,
		 *        "aLengthMenu": [[25, 50, 100, -1], [25, 50, 100, "All"]]
		 *      } );
		 *    } );
		 */
		"aLengthMenu": [ 10, 25, 50, 100 ],
	
	
		/**
		 * The aoColumns option in the initialisation parameter allows you to define
		 * details about the way individual columns behave. For a full list of
		 * column options that can be set, please see 
		 * {@link DataTable.defaults.columns}. Note that if you use aoColumns to
		 * define your columns, you must have an entry in the array for every single
		 * column that you have in your table (these can be null if you don't which
		 * to specify any options).
		 *  @member
		 */
		"aoColumns": null,
	
		/**
		 * Very similar to aoColumns, aoColumnDefs allows you to target a specific 
		 * column, multiple columns, or all columns, using the aTargets property of 
		 * each object in the array. This allows great flexibility when creating 
		 * tables, as the aoColumnDefs arrays can be of any length, targeting the 
		 * columns you specifically want. aoColumnDefs may use any of the column 
		 * options available: {@link DataTable.defaults.columns}, but it _must_
		 * have aTargets defined in each object in the array. Values in the aTargets
		 * array may be:
		 *   <ul>
		 *     <li>a string - class name will be matched on the TH for the column</li>
		 *     <li>0 or a positive integer - column index counting from the left</li>
		 *     <li>a negative integer - column index counting from the right</li>
		 *     <li>the string "_all" - all columns (i.e. assign a default)</li>
		 *   </ul>
		 *  @member
		 */
		"aoColumnDefs": null,
	
	
		/**
		 * Basically the same as oSearch, this parameter defines the individual column
		 * filtering state at initialisation time. The array must be of the same size 
		 * as the number of columns, and each element be an object with the parameters
		 * "sSearch" and "bEscapeRegex" (the latter is optional). 'null' is also
		 * accepted and the default will be used.
		 *  @type array
		 *  @default []
		 *  @dtopt Option
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoSearchCols": [
		 *          null,
		 *          { "sSearch": "My filter" },
		 *          null,
		 *          { "sSearch": "^[0-9]", "bEscapeRegex": false }
		 *        ]
		 *      } );
		 *    } )
		 */
		"aoSearchCols": [],
	
	
		/**
		 * An array of CSS classes that should be applied to displayed rows. This 
		 * array may be of any length, and DataTables will apply each class 
		 * sequentially, looping when required.
		 *  @type array
		 *  @default null <i>Will take the values determined by the oClasses.sStripe*
		 *    options</i>
		 *  @dtopt Option
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "asStripeClasses": [ 'strip1', 'strip2', 'strip3' ]
		 *      } );
		 *    } )
		 */
		"asStripeClasses": null,
	
	
		/**
		 * Enable or disable automatic column width calculation. This can be disabled
		 * as an optimisation (it takes some time to calculate the widths) if the
		 * tables widths are passed in using aoColumns.
		 *  @type boolean
		 *  @default true
		 *  @dtopt Features
		 * 
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "bAutoWidth": false
		 *      } );
		 *    } );
		 */
		"bAutoWidth": true,
	
	
		/**
		 * Deferred rendering can provide DataTables with a huge speed boost when you
		 * are using an Ajax or JS data source for the table. This option, when set to
		 * true, will cause DataTables to defer the creation of the table elements for
		 * each row until they are needed for a draw - saving a significant amount of
		 * time.
		 *  @type boolean
		 *  @default false
		 *  @dtopt Features
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      var oTable = $('#example').dataTable( {
		 *        "sAjaxSource": "sources/arrays.txt",
		 *        "bDeferRender": true
		 *      } );
		 *    } );
		 */
		"bDeferRender": false,
	
	
		/**
		 * Replace a DataTable which matches the given selector and replace it with 
		 * one which has the properties of the new initialisation object passed. If no
		 * table matches the selector, then the new DataTable will be constructed as
		 * per normal.
		 *  @type boolean
		 *  @default false
		 *  @dtopt Options
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "sScrollY": "200px",
		 *        "bPaginate": false
		 *      } );
		 *      
		 *      // Some time later....
		 *      $('#example').dataTable( {
		 *        "bFilter": false,
		 *        "bDestroy": true
		 *      } );
		 *    } );
		 */
		"bDestroy": false,
	
	
		/**
		 * Enable or disable filtering of data. Filtering in DataTables is "smart" in
		 * that it allows the end user to input multiple words (space separated) and
		 * will match a row containing those words, even if not in the order that was
		 * specified (this allow matching across multiple columns). Note that if you
		 * wish to use filtering in DataTables this must remain 'true' - to remove the
		 * default filtering input box and retain filtering abilities, please use
		 * {@link DataTable.defaults.sDom}.
		 *  @type boolean
		 *  @default true
		 *  @dtopt Features
		 * 
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "bFilter": false
		 *      } );
		 *    } );
		 */
		"bFilter": true,
	
	
		/**
		 * Enable or disable the table information display. This shows information 
		 * about the data that is currently visible on the page, including information
		 * about filtered data if that action is being performed.
		 *  @type boolean
		 *  @default true
		 *  @dtopt Features
		 * 
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "bInfo": false
		 *      } );
		 *    } );
		 */
		"bInfo": true,
	
	
		/**
		 * Enable jQuery UI ThemeRoller support (required as ThemeRoller requires some
		 * slightly different and additional mark-up from what DataTables has
		 * traditionally used).
		 *  @type boolean
		 *  @default false
		 *  @dtopt Features
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "bJQueryUI": true
		 *      } );
		 *    } );
		 */
		"bJQueryUI": false,
	
	
		/**
		 * Allows the end user to select the size of a formatted page from a select
		 * menu (sizes are 10, 25, 50 and 100). Requires pagination (bPaginate).
		 *  @type boolean
		 *  @default true
		 *  @dtopt Features
		 * 
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "bLengthChange": false
		 *      } );
		 *    } );
		 */
		"bLengthChange": true,
	
	
		/**
		 * Enable or disable pagination.
		 *  @type boolean
		 *  @default true
		 *  @dtopt Features
		 * 
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "bPaginate": false
		 *      } );
		 *    } );
		 */
		"bPaginate": true,
	
	
		/**
		 * Enable or disable the display of a 'processing' indicator when the table is
		 * being processed (e.g. a sort). This is particularly useful for tables with
		 * large amounts of data where it can take a noticeable amount of time to sort
		 * the entries.
		 *  @type boolean
		 *  @default false
		 *  @dtopt Features
		 * 
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "bProcessing": true
		 *      } );
		 *    } );
		 */
		"bProcessing": false,
	
	
		/**
		 * Retrieve the DataTables object for the given selector. Note that if the
		 * table has already been initialised, this parameter will cause DataTables
		 * to simply return the object that has already been set up - it will not take
		 * account of any changes you might have made to the initialisation object
		 * passed to DataTables (setting this parameter to true is an acknowledgement
		 * that you understand this). bDestroy can be used to reinitialise a table if
		 * you need.
		 *  @type boolean
		 *  @default false
		 *  @dtopt Options
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      initTable();
		 *      tableActions();
		 *    } );
		 *    
		 *    function initTable ()
		 *    {
		 *      return $('#example').dataTable( {
		 *        "sScrollY": "200px",
		 *        "bPaginate": false,
		 *        "bRetrieve": true
		 *      } );
		 *    }
		 *    
		 *    function tableActions ()
		 *    {
		 *      var oTable = initTable();
		 *      // perform API operations with oTable 
		 *    }
		 */
		"bRetrieve": false,
	
	
		/**
		 * Indicate if DataTables should be allowed to set the padding / margin
		 * etc for the scrolling header elements or not. Typically you will want
		 * this.
		 *  @type boolean
		 *  @default true
		 *  @dtopt Options
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "bScrollAutoCss": false,
		 *        "sScrollY": "200px"
		 *      } );
		 *    } );
		 */
		"bScrollAutoCss": true,
	
	
		/**
		 * When vertical (y) scrolling is enabled, DataTables will force the height of
		 * the table's viewport to the given height at all times (useful for layout).
		 * However, this can look odd when filtering data down to a small data set,
		 * and the footer is left "floating" further down. This parameter (when
		 * enabled) will cause DataTables to collapse the table's viewport down when
		 * the result set will fit within the given Y height.
		 *  @type boolean
		 *  @default false
		 *  @dtopt Options
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "sScrollY": "200",
		 *        "bScrollCollapse": true
		 *      } );
		 *    } );
		 */
		"bScrollCollapse": false,
	
	
		/**
		 * Enable infinite scrolling for DataTables (to be used in combination with
		 * sScrollY). Infinite scrolling means that DataTables will continually load
		 * data as a user scrolls through a table, which is very useful for large
		 * dataset. This cannot be used with pagination, which is automatically
		 * disabled. Note - the Scroller extra for DataTables is recommended in
		 * in preference to this option.
		 *  @type boolean
		 *  @default false
		 *  @dtopt Features
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "bScrollInfinite": true,
		 *        "bScrollCollapse": true,
		 *        "sScrollY": "200px"
		 *      } );
		 *    } );
		 */
		"bScrollInfinite": false,
	
	
		/**
		 * Configure DataTables to use server-side processing. Note that the
		 * sAjaxSource parameter must also be given in order to give DataTables a
		 * source to obtain the required data for each draw.
		 *  @type boolean
		 *  @default false
		 *  @dtopt Features
		 *  @dtopt Server-side
		 * 
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "bServerSide": true,
		 *        "sAjaxSource": "xhr.php"
		 *      } );
		 *    } );
		 */
		"bServerSide": false,
	
	
		/**
		 * Enable or disable sorting of columns. Sorting of individual columns can be
		 * disabled by the "bSortable" option for each column.
		 *  @type boolean
		 *  @default true
		 *  @dtopt Features
		 * 
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "bSort": false
		 *      } );
		 *    } );
		 */
		"bSort": true,
	
	
		/**
		 * Allows control over whether DataTables should use the top (true) unique
		 * cell that is found for a single column, or the bottom (false - default).
		 * This is useful when using complex headers.
		 *  @type boolean
		 *  @default false
		 *  @dtopt Options
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "bSortCellsTop": true
		 *      } );
		 *    } );
		 */
		"bSortCellsTop": false,
	
	
		/**
		 * Enable or disable the addition of the classes 'sorting_1', 'sorting_2' and
		 * 'sorting_3' to the columns which are currently being sorted on. This is
		 * presented as a feature switch as it can increase processing time (while
		 * classes are removed and added) so for large data sets you might want to
		 * turn this off.
		 *  @type boolean
		 *  @default true
		 *  @dtopt Features
		 * 
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "bSortClasses": false
		 *      } );
		 *    } );
		 */
		"bSortClasses": true,
	
	
		/**
		 * Enable or disable state saving. When enabled a cookie will be used to save
		 * table display information such as pagination information, display length,
		 * filtering and sorting. As such when the end user reloads the page the
		 * display display will match what thy had previously set up.
		 *  @type boolean
		 *  @default false
		 *  @dtopt Features
		 * 
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "bStateSave": true
		 *      } );
		 *    } );
		 */
		"bStateSave": false,
	
	
		/**
		 * Customise the cookie and / or the parameters being stored when using
		 * DataTables with state saving enabled. This function is called whenever
		 * the cookie is modified, and it expects a fully formed cookie string to be
		 * returned. Note that the data object passed in is a Javascript object which
		 * must be converted to a string (JSON.stringify for example).
		 *  @type function
		 *  @param {string} sName Name of the cookie defined by DataTables
		 *  @param {object} oData Data to be stored in the cookie
		 *  @param {string} sExpires Cookie expires string
		 *  @param {string} sPath Path of the cookie to set
		 *  @returns {string} Cookie formatted string (which should be encoded by
		 *    using encodeURIComponent())
		 *  @dtopt Callbacks
		 * 
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "fnCookieCallback": function (sName, oData, sExpires, sPath) {
		 *          // Customise oData or sName or whatever else here
		 *          return sName + "="+JSON.stringify(oData)+"; expires=" + sExpires +"; path=" + sPath;
		 *        }
		 *      } );
		 *    } );
		 */
		"fnCookieCallback": null,
	
	
		/**
		 * This function is called when a TR element is created (and all TD child
		 * elements have been inserted), or registered if using a DOM source, allowing
		 * manipulation of the TR element (adding classes etc).
		 *  @type function
		 *  @param {node} nRow "TR" element for the current row
		 *  @param {array} aData Raw data array for this row
		 *  @param {int} iDataIndex The index of this row in aoData
		 *  @dtopt Callbacks
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "fnCreatedRow": function( nRow, aData, iDataIndex ) {
		 *          // Bold the grade for all 'A' grade browsers
		 *          if ( aData[4] == "A" )
		 *          {
		 *            $('td:eq(4)', nRow).html( '<b>A</b>' );
		 *          }
		 *        }
		 *      } );
		 *    } );
		 */
		"fnCreatedRow": null,
	
	
		/**
		 * This function is called on every 'draw' event, and allows you to
		 * dynamically modify any aspect you want about the created DOM.
		 *  @type function
		 *  @param {object} oSettings DataTables settings object
		 *  @dtopt Callbacks
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "fnDrawCallback": function( oSettings ) {
		 *          alert( 'DataTables has redrawn the table' );
		 *        }
		 *      } );
		 *    } );
		 */
		"fnDrawCallback": null,
	
	
		/**
		 * Identical to fnHeaderCallback() but for the table footer this function
		 * allows you to modify the table footer on every 'draw' even.
		 *  @type function
		 *  @param {node} nFoot "TR" element for the footer
		 *  @param {array} aData Full table data (as derived from the original HTML)
		 *  @param {int} iStart Index for the current display starting point in the 
		 *    display array
		 *  @param {int} iEnd Index for the current display ending point in the 
		 *    display array
		 *  @param {array int} aiDisplay Index array to translate the visual position
		 *    to the full data array
		 *  @dtopt Callbacks
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "fnFooterCallback": function( nFoot, aData, iStart, iEnd, aiDisplay ) {
		 *          nFoot.getElementsByTagName('th')[0].innerHTML = "Starting index is "+iStart;
		 *        }
		 *      } );
		 *    } )
		 */
		"fnFooterCallback": null,
	
	
		/**
		 * When rendering large numbers in the information element for the table
		 * (i.e. "Showing 1 to 10 of 57 entries") DataTables will render large numbers
		 * to have a comma separator for the 'thousands' units (e.g. 1 million is
		 * rendered as "1,000,000") to help readability for the end user. This
		 * function will override the default method DataTables uses.
		 *  @type function
		 *  @member
		 *  @param {int} iIn number to be formatted
		 *  @returns {string} formatted string for DataTables to show the number
		 *  @dtopt Callbacks
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "fnFormatNumber": function ( iIn ) {
		 *          if ( iIn &lt; 1000 ) {
		 *            return iIn;
		 *          } else {
		 *            var 
		 *              s=(iIn+""), 
		 *              a=s.split(""), out="", 
		 *              iLen=s.length;
		 *            
		 *            for ( var i=0 ; i&lt;iLen ; i++ ) {
		 *              if ( i%3 === 0 &amp;&amp; i !== 0 ) {
		 *                out = "'"+out;
		 *              }
		 *              out = a[iLen-i-1]+out;
		 *            }
		 *          }
		 *          return out;
		 *        };
		 *      } );
		 *    } );
		 */
		"fnFormatNumber": function ( iIn ) {
			if ( iIn < 1000 )
			{
				// A small optimisation for what is likely to be the majority of use cases
				return iIn;
			}
	
			var s=(iIn+""), a=s.split(""), out="", iLen=s.length;
			
			for ( var i=0 ; i<iLen ; i++ )
			{
				if ( i%3 === 0 && i !== 0 )
				{
					out = this.oLanguage.sInfoThousands+out;
				}
				out = a[iLen-i-1]+out;
			}
			return out;
		},
	
	
		/**
		 * This function is called on every 'draw' event, and allows you to
		 * dynamically modify the header row. This can be used to calculate and
		 * display useful information about the table.
		 *  @type function
		 *  @param {node} nHead "TR" element for the header
		 *  @param {array} aData Full table data (as derived from the original HTML)
		 *  @param {int} iStart Index for the current display starting point in the
		 *    display array
		 *  @param {int} iEnd Index for the current display ending point in the
		 *    display array
		 *  @param {array int} aiDisplay Index array to translate the visual position
		 *    to the full data array
		 *  @dtopt Callbacks
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "fnHeaderCallback": function( nHead, aData, iStart, iEnd, aiDisplay ) {
		 *          nHead.getElementsByTagName('th')[0].innerHTML = "Displaying "+(iEnd-iStart)+" records";
		 *        }
		 *      } );
		 *    } )
		 */
		"fnHeaderCallback": null,
	
	
		/**
		 * The information element can be used to convey information about the current
		 * state of the table. Although the internationalisation options presented by
		 * DataTables are quite capable of dealing with most customisations, there may
		 * be times where you wish to customise the string further. This callback
		 * allows you to do exactly that.
		 *  @type function
		 *  @param {object} oSettings DataTables settings object
		 *  @param {int} iStart Starting position in data for the draw
		 *  @param {int} iEnd End position in data for the draw
		 *  @param {int} iMax Total number of rows in the table (regardless of
		 *    filtering)
		 *  @param {int} iTotal Total number of rows in the data set, after filtering
		 *  @param {string} sPre The string that DataTables has formatted using it's
		 *    own rules
		 *  @returns {string} The string to be displayed in the information element.
		 *  @dtopt Callbacks
		 * 
		 *  @example
		 *    $('#example').dataTable( {
		 *      "fnInfoCallback": function( oSettings, iStart, iEnd, iMax, iTotal, sPre ) {
		 *        return iStart +" to "+ iEnd;
		 *      }
		 *    } );
		 */
		"fnInfoCallback": null,
	
	
		/**
		 * Called when the table has been initialised. Normally DataTables will
		 * initialise sequentially and there will be no need for this function,
		 * however, this does not hold true when using external language information
		 * since that is obtained using an async XHR call.
		 *  @type function
		 *  @param {object} oSettings DataTables settings object
		 *  @param {object} json The JSON object request from the server - only
		 *    present if client-side Ajax sourced data is used
		 *  @dtopt Callbacks
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "fnInitComplete": function(oSettings, json) {
		 *          alert( 'DataTables has finished its initialisation.' );
		 *        }
		 *      } );
		 *    } )
		 */
		"fnInitComplete": null,
	
	
		/**
		 * Called at the very start of each table draw and can be used to cancel the
		 * draw by returning false, any other return (including undefined) results in
		 * the full draw occurring).
		 *  @type function
		 *  @param {object} oSettings DataTables settings object
		 *  @returns {boolean} False will cancel the draw, anything else (including no
		 *    return) will allow it to complete.
		 *  @dtopt Callbacks
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "fnPreDrawCallback": function( oSettings ) {
		 *          if ( $('#test').val() == 1 ) {
		 *            return false;
		 *          }
		 *        }
		 *      } );
		 *    } );
		 */
		"fnPreDrawCallback": null,
	
	
		/**
		 * This function allows you to 'post process' each row after it have been
		 * generated for each table draw, but before it is rendered on screen. This
		 * function might be used for setting the row class name etc.
		 *  @type function
		 *  @param {node} nRow "TR" element for the current row
		 *  @param {array} aData Raw data array for this row
		 *  @param {int} iDisplayIndex The display index for the current table draw
		 *  @param {int} iDisplayIndexFull The index of the data in the full list of
		 *    rows (after filtering)
		 *  @dtopt Callbacks
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "fnRowCallback": function( nRow, aData, iDisplayIndex, iDisplayIndexFull ) {
		 *          // Bold the grade for all 'A' grade browsers
		 *          if ( aData[4] == "A" )
		 *          {
		 *            $('td:eq(4)', nRow).html( '<b>A</b>' );
		 *          }
		 *        }
		 *      } );
		 *    } );
		 */
		"fnRowCallback": null,
	
	
		/**
		 * This parameter allows you to override the default function which obtains
		 * the data from the server ($.getJSON) so something more suitable for your
		 * application. For example you could use POST data, or pull information from
		 * a Gears or AIR database.
		 *  @type function
		 *  @member
		 *  @param {string} sSource HTTP source to obtain the data from (sAjaxSource)
		 *  @param {array} aoData A key/value pair object containing the data to send
		 *    to the server
		 *  @param {function} fnCallback to be called on completion of the data get
		 *    process that will draw the data on the page.
		 *  @param {object} oSettings DataTables settings object
		 *  @dtopt Callbacks
		 *  @dtopt Server-side
		 * 
		 *  @example
		 *    // POST data to server
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "bProcessing": true,
		 *        "bServerSide": true,
		 *        "sAjaxSource": "xhr.php",
		 *        "fnServerData": function ( sSource, aoData, fnCallback, oSettings ) {
		 *          oSettings.jqXHR = $.ajax( {
		 *            "dataType": 'json', 
		 *            "type": "POST", 
		 *            "url": sSource, 
		 *            "data": aoData, 
		 *            "success": fnCallback
		 *          } );
		 *        }
		 *      } );
		 *    } );
		 */
		"fnServerData": function ( sUrl, aoData, fnCallback, oSettings ) {
			oSettings.jqXHR = $.ajax( {
				"url":  sUrl,
				"data": aoData,
				"success": function (json) {
					if ( json.sError ) {
						oSettings.oApi._fnLog( oSettings, 0, json.sError );
					}
					
					$(oSettings.oInstance).trigger('xhr', [oSettings, json]);
					fnCallback( json );
				},
				"dataType": "json",
				"cache": false,
				"type": oSettings.sServerMethod,
				"error": function (xhr, error, thrown) {
					if ( error == "parsererror" ) {
						oSettings.oApi._fnLog( oSettings, 0, "DataTables warning: JSON data from "+
							"server could not be parsed. This is caused by a JSON formatting error." );
					}
				}
			} );
		},
	
	
		/**
		 * It is often useful to send extra data to the server when making an Ajax
		 * request - for example custom filtering information, and this callback
		 * function makes it trivial to send extra information to the server. The
		 * passed in parameter is the data set that has been constructed by
		 * DataTables, and you can add to this or modify it as you require.
		 *  @type function
		 *  @param {array} aoData Data array (array of objects which are name/value
		 *    pairs) that has been constructed by DataTables and will be sent to the
		 *    server. In the case of Ajax sourced data with server-side processing
		 *    this will be an empty array, for server-side processing there will be a
		 *    significant number of parameters!
		 *  @returns {undefined} Ensure that you modify the aoData array passed in,
		 *    as this is passed by reference.
		 *  @dtopt Callbacks
		 *  @dtopt Server-side
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "bProcessing": true,
		 *        "bServerSide": true,
		 *        "sAjaxSource": "scripts/server_processing.php",
		 *        "fnServerParams": function ( aoData ) {
		 *          aoData.push( { "name": "more_data", "value": "my_value" } );
		 *        }
		 *      } );
		 *    } );
		 */
		"fnServerParams": null,
	
	
		/**
		 * Load the table state. With this function you can define from where, and how, the
		 * state of a table is loaded. By default DataTables will load from its state saving
		 * cookie, but you might wish to use local storage (HTML5) or a server-side database.
		 *  @type function
		 *  @member
		 *  @param {object} oSettings DataTables settings object
		 *  @return {object} The DataTables state object to be loaded
		 *  @dtopt Callbacks
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "bStateSave": true,
		 *        "fnStateLoad": function (oSettings) {
		 *          var o;
		 *          
		 *          // Send an Ajax request to the server to get the data. Note that
		 *          // this is a synchronous request.
		 *          $.ajax( {
		 *            "url": "/state_load",
		 *            "async": false,
		 *            "dataType": "json",
		 *            "success": function (json) {
		 *              o = json;
		 *            }
		 *          } );
		 *          
		 *          return o;
		 *        }
		 *      } );
		 *    } );
		 */
		"fnStateLoad": function ( oSettings ) {
			var sData = this.oApi._fnReadCookie( oSettings.sCookiePrefix+oSettings.sInstance );
			var oData;
	
			try {
				oData = (typeof $.parseJSON === 'function') ? 
					$.parseJSON(sData) : eval( '('+sData+')' );
			} catch (e) {
				oData = null;
			}
	
			return oData;
		},
	
	
		/**
		 * Callback which allows modification of the saved state prior to loading that state.
		 * This callback is called when the table is loading state from the stored data, but
		 * prior to the settings object being modified by the saved state. Note that for 
		 * plug-in authors, you should use the 'stateLoadParams' event to load parameters for 
		 * a plug-in.
		 *  @type function
		 *  @param {object} oSettings DataTables settings object
		 *  @param {object} oData The state object that is to be loaded
		 *  @dtopt Callbacks
		 * 
		 *  @example
		 *    // Remove a saved filter, so filtering is never loaded
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "bStateSave": true,
		 *        "fnStateLoadParams": function (oSettings, oData) {
		 *          oData.oSearch.sSearch = "";
		 *        }
		 *      } );
		 *    } );
		 * 
		 *  @example
		 *    // Disallow state loading by returning false
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "bStateSave": true,
		 *        "fnStateLoadParams": function (oSettings, oData) {
		 *          return false;
		 *        }
		 *      } );
		 *    } );
		 */
		"fnStateLoadParams": null,
	
	
		/**
		 * Callback that is called when the state has been loaded from the state saving method
		 * and the DataTables settings object has been modified as a result of the loaded state.
		 *  @type function
		 *  @param {object} oSettings DataTables settings object
		 *  @param {object} oData The state object that was loaded
		 *  @dtopt Callbacks
		 * 
		 *  @example
		 *    // Show an alert with the filtering value that was saved
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "bStateSave": true,
		 *        "fnStateLoaded": function (oSettings, oData) {
		 *          alert( 'Saved filter was: '+oData.oSearch.sSearch );
		 *        }
		 *      } );
		 *    } );
		 */
		"fnStateLoaded": null,
	
	
		/**
		 * Save the table state. This function allows you to define where and how the state
		 * information for the table is stored - by default it will use a cookie, but you
		 * might want to use local storage (HTML5) or a server-side database.
		 *  @type function
		 *  @member
		 *  @param {object} oSettings DataTables settings object
		 *  @param {object} oData The state object to be saved
		 *  @dtopt Callbacks
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "bStateSave": true,
		 *        "fnStateSave": function (oSettings, oData) {
		 *          // Send an Ajax request to the server with the state object
		 *          $.ajax( {
		 *            "url": "/state_save",
		 *            "data": oData,
		 *            "dataType": "json",
		 *            "method": "POST"
		 *            "success": function () {}
		 *          } );
		 *        }
		 *      } );
		 *    } );
		 */
		"fnStateSave": function ( oSettings, oData ) {
			this.oApi._fnCreateCookie( 
				oSettings.sCookiePrefix+oSettings.sInstance, 
				this.oApi._fnJsonString(oData), 
				oSettings.iCookieDuration, 
				oSettings.sCookiePrefix, 
				oSettings.fnCookieCallback
			);
		},
	
	
		/**
		 * Callback which allows modification of the state to be saved. Called when the table 
		 * has changed state a new state save is required. This method allows modification of
		 * the state saving object prior to actually doing the save, including addition or 
		 * other state properties or modification. Note that for plug-in authors, you should 
		 * use the 'stateSaveParams' event to save parameters for a plug-in.
		 *  @type function
		 *  @param {object} oSettings DataTables settings object
		 *  @param {object} oData The state object to be saved
		 *  @dtopt Callbacks
		 * 
		 *  @example
		 *    // Remove a saved filter, so filtering is never saved
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "bStateSave": true,
		 *        "fnStateSaveParams": function (oSettings, oData) {
		 *          oData.oSearch.sSearch = "";
		 *        }
		 *      } );
		 *    } );
		 */
		"fnStateSaveParams": null,
	
	
		/**
		 * Duration of the cookie which is used for storing session information. This
		 * value is given in seconds.
		 *  @type int
		 *  @default 7200 <i>(2 hours)</i>
		 *  @dtopt Options
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "iCookieDuration": 60*60*24; // 1 day
		 *      } );
		 *    } )
		 */
		"iCookieDuration": 7200,
	
	
		/**
		 * When enabled DataTables will not make a request to the server for the first
		 * page draw - rather it will use the data already on the page (no sorting etc
		 * will be applied to it), thus saving on an XHR at load time. iDeferLoading
		 * is used to indicate that deferred loading is required, but it is also used
		 * to tell DataTables how many records there are in the full table (allowing
		 * the information element and pagination to be displayed correctly). In the case
		 * where a filtering is applied to the table on initial load, this can be
		 * indicated by giving the parameter as an array, where the first element is
		 * the number of records available after filtering and the second element is the
		 * number of records without filtering (allowing the table information element
		 * to be shown correctly).
		 *  @type int | array
		 *  @default null
		 *  @dtopt Options
		 * 
		 *  @example
		 *    // 57 records available in the table, no filtering applied
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "bServerSide": true,
		 *        "sAjaxSource": "scripts/server_processing.php",
		 *        "iDeferLoading": 57
		 *      } );
		 *    } );
		 * 
		 *  @example
		 *    // 57 records after filtering, 100 without filtering (an initial filter applied)
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "bServerSide": true,
		 *        "sAjaxSource": "scripts/server_processing.php",
		 *        "iDeferLoading": [ 57, 100 ],
		 *        "oSearch": {
		 *          "sSearch": "my_filter"
		 *        }
		 *      } );
		 *    } );
		 */
		"iDeferLoading": null,
	
	
		/**
		 * Number of rows to display on a single page when using pagination. If
		 * feature enabled (bLengthChange) then the end user will be able to override
		 * this to a custom setting using a pop-up menu.
		 *  @type int
		 *  @default 10
		 *  @dtopt Options
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "iDisplayLength": 50
		 *      } );
		 *    } )
		 */
		"iDisplayLength": 10,
	
	
		/**
		 * Define the starting point for data display when using DataTables with
		 * pagination. Note that this parameter is the number of records, rather than
		 * the page number, so if you have 10 records per page and want to start on
		 * the third page, it should be "20".
		 *  @type int
		 *  @default 0
		 *  @dtopt Options
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "iDisplayStart": 20
		 *      } );
		 *    } )
		 */
		"iDisplayStart": 0,
	
	
		/**
		 * The scroll gap is the amount of scrolling that is left to go before
		 * DataTables will load the next 'page' of data automatically. You typically
		 * want a gap which is big enough that the scrolling will be smooth for the
		 * user, while not so large that it will load more data than need.
		 *  @type int
		 *  @default 100
		 *  @dtopt Options
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "bScrollInfinite": true,
		 *        "bScrollCollapse": true,
		 *        "sScrollY": "200px",
		 *        "iScrollLoadGap": 50
		 *      } );
		 *    } );
		 */
		"iScrollLoadGap": 100,
	
	
		/**
		 * By default DataTables allows keyboard navigation of the table (sorting, paging,
		 * and filtering) by adding a tabindex attribute to the required elements. This
		 * allows you to tab through the controls and press the enter key to activate them.
		 * The tabindex is default 0, meaning that the tab follows the flow of the document.
		 * You can overrule this using this parameter if you wish. Use a value of -1 to
		 * disable built-in keyboard navigation.
		 *  @type int
		 *  @default 0
		 *  @dtopt Options
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "iTabIndex": 1
		 *      } );
		 *    } );
		 */
		"iTabIndex": 0,
	
	
		/**
		 * All strings that DataTables uses in the user interface that it creates
		 * are defined in this object, allowing you to modified them individually or
		 * completely replace them all as required.
		 *  @namespace
		 */
		"oLanguage": {
			/**
			 * Strings that are used for WAI-ARIA labels and controls only (these are not
			 * actually visible on the page, but will be read by screenreaders, and thus
			 * must be internationalised as well).
			 *  @namespace
			 */
			"oAria": {
				/**
				 * ARIA label that is added to the table headers when the column may be
				 * sorted ascending by activing the column (click or return when focused).
				 * Note that the column header is prefixed to this string.
				 *  @type string
				 *  @default : activate to sort column ascending
				 *  @dtopt Language
				 * 
				 *  @example
				 *    $(document).ready( function() {
				 *      $('#example').dataTable( {
				 *        "oLanguage": {
				 *          "oAria": {
				 *            "sSortAscending": " - click/return to sort ascending"
				 *          }
				 *        }
				 *      } );
				 *    } );
				 */
				"sSortAscending": ": activate to sort column ascending",
	
				/**
				 * ARIA label that is added to the table headers when the column may be
				 * sorted descending by activing the column (click or return when focused).
				 * Note that the column header is prefixed to this string.
				 *  @type string
				 *  @default : activate to sort column ascending
				 *  @dtopt Language
				 * 
				 *  @example
				 *    $(document).ready( function() {
				 *      $('#example').dataTable( {
				 *        "oLanguage": {
				 *          "oAria": {
				 *            "sSortDescending": " - click/return to sort descending"
				 *          }
				 *        }
				 *      } );
				 *    } );
				 */
				"sSortDescending": ": activate to sort column descending"
			},
	
			/**
			 * Pagination string used by DataTables for the two built-in pagination
			 * control types ("two_button" and "full_numbers")
			 *  @namespace
			 */
			"oPaginate": {
				/**
				 * Text to use when using the 'full_numbers' type of pagination for the
				 * button to take the user to the first page.
				 *  @type string
				 *  @default First
				 *  @dtopt Language
				 * 
				 *  @example
				 *    $(document).ready( function() {
				 *      $('#example').dataTable( {
				 *        "oLanguage": {
				 *          "oPaginate": {
				 *            "sFirst": "First page"
				 *          }
				 *        }
				 *      } );
				 *    } );
				 */
				"sFirst": "First",
			
			
				/**
				 * Text to use when using the 'full_numbers' type of pagination for the
				 * button to take the user to the last page.
				 *  @type string
				 *  @default Last
				 *  @dtopt Language
				 * 
				 *  @example
				 *    $(document).ready( function() {
				 *      $('#example').dataTable( {
				 *        "oLanguage": {
				 *          "oPaginate": {
				 *            "sLast": "Last page"
				 *          }
				 *        }
				 *      } );
				 *    } );
				 */
				"sLast": "Last",
			
			
				/**
				 * Text to use for the 'next' pagination button (to take the user to the 
				 * next page).
				 *  @type string
				 *  @default Next
				 *  @dtopt Language
				 * 
				 *  @example
				 *    $(document).ready( function() {
				 *      $('#example').dataTable( {
				 *        "oLanguage": {
				 *          "oPaginate": {
				 *            "sNext": "Next page"
				 *          }
				 *        }
				 *      } );
				 *    } );
				 */
				"sNext": "Next",
			
			
				/**
				 * Text to use for the 'previous' pagination button (to take the user to  
				 * the previous page).
				 *  @type string
				 *  @default Previous
				 *  @dtopt Language
				 * 
				 *  @example
				 *    $(document).ready( function() {
				 *      $('#example').dataTable( {
				 *        "oLanguage": {
				 *          "oPaginate": {
				 *            "sPrevious": "Previous page"
				 *          }
				 *        }
				 *      } );
				 *    } );
				 */
				"sPrevious": "Previous"
			},
		
			/**
			 * This string is shown in preference to sZeroRecords when the table is
			 * empty of data (regardless of filtering). Note that this is an optional
			 * parameter - if it is not given, the value of sZeroRecords will be used
			 * instead (either the default or given value).
			 *  @type string
			 *  @default No data available in table
			 *  @dtopt Language
			 * 
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "oLanguage": {
			 *          "sEmptyTable": "No data available in table"
			 *        }
			 *      } );
			 *    } );
			 */
			"sEmptyTable": "No data available in table",
		
		
			/**
			 * This string gives information to the end user about the information that 
			 * is current on display on the page. The _START_, _END_ and _TOTAL_ 
			 * variables are all dynamically replaced as the table display updates, and 
			 * can be freely moved or removed as the language requirements change.
			 *  @type string
			 *  @default Showing _START_ to _END_ of _TOTAL_ entries
			 *  @dtopt Language
			 * 
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "oLanguage": {
			 *          "sInfo": "Got a total of _TOTAL_ entries to show (_START_ to _END_)"
			 *        }
			 *      } );
			 *    } );
			 */
			"sInfo": "Showing _START_ to _END_ of _TOTAL_ entries",
		
		
			/**
			 * Display information string for when the table is empty. Typically the 
			 * format of this string should match sInfo.
			 *  @type string
			 *  @default Showing 0 to 0 of 0 entries
			 *  @dtopt Language
			 * 
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "oLanguage": {
			 *          "sInfoEmpty": "No entries to show"
			 *        }
			 *      } );
			 *    } );
			 */
			"sInfoEmpty": "Showing 0 to 0 of 0 entries",
		
		
			/**
			 * When a user filters the information in a table, this string is appended 
			 * to the information (sInfo) to give an idea of how strong the filtering 
			 * is. The variable _MAX_ is dynamically updated.
			 *  @type string
			 *  @default (filtered from _MAX_ total entries)
			 *  @dtopt Language
			 * 
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "oLanguage": {
			 *          "sInfoFiltered": " - filtering from _MAX_ records"
			 *        }
			 *      } );
			 *    } );
			 */
			"sInfoFiltered": "(filtered from _MAX_ total entries)",
		
		
			/**
			 * If can be useful to append extra information to the info string at times,
			 * and this variable does exactly that. This information will be appended to
			 * the sInfo (sInfoEmpty and sInfoFiltered in whatever combination they are
			 * being used) at all times.
			 *  @type string
			 *  @default <i>Empty string</i>
			 *  @dtopt Language
			 * 
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "oLanguage": {
			 *          "sInfoPostFix": "All records shown are derived from real information."
			 *        }
			 *      } );
			 *    } );
			 */
			"sInfoPostFix": "",
		
		
			/**
			 * DataTables has a build in number formatter (fnFormatNumber) which is used
			 * to format large numbers that are used in the table information. By
			 * default a comma is used, but this can be trivially changed to any
			 * character you wish with this parameter.
			 *  @type string
			 *  @default ,
			 *  @dtopt Language
			 * 
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "oLanguage": {
			 *          "sInfoThousands": "'"
			 *        }
			 *      } );
			 *    } );
			 */
			"sInfoThousands": ",",
		
		
			/**
			 * Detail the action that will be taken when the drop down menu for the
			 * pagination length option is changed. The '_MENU_' variable is replaced
			 * with a default select list of 10, 25, 50 and 100, and can be replaced
			 * with a custom select box if required.
			 *  @type string
			 *  @default Show _MENU_ entries
			 *  @dtopt Language
			 * 
			 *  @example
			 *    // Language change only
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "oLanguage": {
			 *          "sLengthMenu": "Display _MENU_ records"
			 *        }
			 *      } );
			 *    } );
			 *    
			 *  @example
			 *    // Language and options change
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "oLanguage": {
			 *          "sLengthMenu": 'Display <select>'+
			 *            '<option value="10">10</option>'+
			 *            '<option value="20">20</option>'+
			 *            '<option value="30">30</option>'+
			 *            '<option value="40">40</option>'+
			 *            '<option value="50">50</option>'+
			 *            '<option value="-1">All</option>'+
			 *            '</select> records'
			 *        }
			 *      } );
			 *    } );
			 */
			"sLengthMenu": "Show _MENU_ entries",
		
		
			/**
			 * When using Ajax sourced data and during the first draw when DataTables is
			 * gathering the data, this message is shown in an empty row in the table to
			 * indicate to the end user the the data is being loaded. Note that this
			 * parameter is not used when loading data by server-side processing, just
			 * Ajax sourced data with client-side processing.
			 *  @type string
			 *  @default Loading...
			 *  @dtopt Language
			 * 
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "oLanguage": {
			 *          "sLoadingRecords": "Please wait - loading..."
			 *        }
			 *      } );
			 *    } );
			 */
			"sLoadingRecords": "Loading...",
		
		
			/**
			 * Text which is displayed when the table is processing a user action
			 * (usually a sort command or similar).
			 *  @type string
			 *  @default Processing...
			 *  @dtopt Language
			 * 
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "oLanguage": {
			 *          "sProcessing": "DataTables is currently busy"
			 *        }
			 *      } );
			 *    } );
			 */
			"sProcessing": "Processing...",
		
		
			/**
			 * Details the actions that will be taken when the user types into the
			 * filtering input text box. The variable "_INPUT_", if used in the string,
			 * is replaced with the HTML text box for the filtering input allowing
			 * control over where it appears in the string. If "_INPUT_" is not given
			 * then the input box is appended to the string automatically.
			 *  @type string
			 *  @default Search:
			 *  @dtopt Language
			 * 
			 *  @example
			 *    // Input text box will be appended at the end automatically
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "oLanguage": {
			 *          "sSearch": "Filter records:"
			 *        }
			 *      } );
			 *    } );
			 *    
			 *  @example
			 *    // Specify where the filter should appear
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "oLanguage": {
			 *          "sSearch": "Apply filter _INPUT_ to table"
			 *        }
			 *      } );
			 *    } );
			 */
			"sSearch": "Search:",
		
		
			/**
			 * All of the language information can be stored in a file on the
			 * server-side, which DataTables will look up if this parameter is passed.
			 * It must store the URL of the language file, which is in a JSON format,
			 * and the object has the same properties as the oLanguage object in the
			 * initialiser object (i.e. the above parameters). Please refer to one of
			 * the example language files to see how this works in action.
			 *  @type string
			 *  @default <i>Empty string - i.e. disabled</i>
			 *  @dtopt Language
			 * 
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "oLanguage": {
			 *          "sUrl": "http://www.sprymedia.co.uk/dataTables/lang.txt"
			 *        }
			 *      } );
			 *    } );
			 */
			"sUrl": "",
		
		
			/**
			 * Text shown inside the table records when the is no information to be
			 * displayed after filtering. sEmptyTable is shown when there is simply no
			 * information in the table at all (regardless of filtering).
			 *  @type string
			 *  @default No matching records found
			 *  @dtopt Language
			 * 
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "oLanguage": {
			 *          "sZeroRecords": "No records to display"
			 *        }
			 *      } );
			 *    } );
			 */
			"sZeroRecords": "No matching records found"
		},
	
	
		/**
		 * This parameter allows you to have define the global filtering state at
		 * initialisation time. As an object the "sSearch" parameter must be
		 * defined, but all other parameters are optional. When "bRegex" is true,
		 * the search string will be treated as a regular expression, when false
		 * (default) it will be treated as a straight string. When "bSmart"
		 * DataTables will use it's smart filtering methods (to word match at
		 * any point in the data), when false this will not be done.
		 *  @namespace
		 *  @extends DataTable.models.oSearch
		 *  @dtopt Options
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "oSearch": {"sSearch": "Initial search"}
		 *      } );
		 *    } )
		 */
		"oSearch": $.extend( {}, DataTable.models.oSearch ),
	
	
		/**
		 * By default DataTables will look for the property 'aaData' when obtaining
		 * data from an Ajax source or for server-side processing - this parameter
		 * allows that property to be changed. You can use Javascript dotted object
		 * notation to get a data source for multiple levels of nesting.
		 *  @type string
		 *  @default aaData
		 *  @dtopt Options
		 *  @dtopt Server-side
		 * 
		 *  @example
		 *    // Get data from { "data": [...] }
		 *    $(document).ready( function() {
		 *      var oTable = $('#example').dataTable( {
		 *        "sAjaxSource": "sources/data.txt",
		 *        "sAjaxDataProp": "data"
		 *      } );
		 *    } );
		 *    
		 *  @example
		 *    // Get data from { "data": { "inner": [...] } }
		 *    $(document).ready( function() {
		 *      var oTable = $('#example').dataTable( {
		 *        "sAjaxSource": "sources/data.txt",
		 *        "sAjaxDataProp": "data.inner"
		 *      } );
		 *    } );
		 */
		"sAjaxDataProp": "aaData",
	
	
		/**
		 * You can instruct DataTables to load data from an external source using this
		 * parameter (use aData if you want to pass data in you already have). Simply
		 * provide a url a JSON object can be obtained from. This object must include
		 * the parameter 'aaData' which is the data source for the table.
		 *  @type string
		 *  @default null
		 *  @dtopt Options
		 *  @dtopt Server-side
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "sAjaxSource": "http://www.sprymedia.co.uk/dataTables/json.php"
		 *      } );
		 *    } )
		 */
		"sAjaxSource": null,
	
	
		/**
		 * This parameter can be used to override the default prefix that DataTables
		 * assigns to a cookie when state saving is enabled.
		 *  @type string
		 *  @default SpryMedia_DataTables_
		 *  @dtopt Options
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "sCookiePrefix": "my_datatable_",
		 *      } );
		 *    } );
		 */
		"sCookiePrefix": "SpryMedia_DataTables_",
	
	
		/**
		 * This initialisation variable allows you to specify exactly where in the
		 * DOM you want DataTables to inject the various controls it adds to the page
		 * (for example you might want the pagination controls at the top of the
		 * table). DIV elements (with or without a custom class) can also be added to
		 * aid styling. The follow syntax is used:
		 *   <ul>
		 *     <li>The following options are allowed:	
		 *       <ul>
		 *         <li>'l' - Length changing</li
		 *         <li>'f' - Filtering input</li>
		 *         <li>'t' - The table!</li>
		 *         <li>'i' - Information</li>
		 *         <li>'p' - Pagination</li>
		 *         <li>'r' - pRocessing</li>
		 *       </ul>
		 *     </li>
		 *     <li>The following constants are allowed:
		 *       <ul>
		 *         <li>'H' - jQueryUI theme "header" classes ('fg-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix')</li>
		 *         <li>'F' - jQueryUI theme "footer" classes ('fg-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix')</li>
		 *       </ul>
		 *     </li>
		 *     <li>The following syntax is expected:
		 *       <ul>
		 *         <li>'&lt;' and '&gt;' - div elements</li>
		 *         <li>'&lt;"class" and '&gt;' - div with a class</li>
		 *         <li>'&lt;"#id" and '&gt;' - div with an ID</li>
		 *       </ul>
		 *     </li>
		 *     <li>Examples:
		 *       <ul>
		 *         <li>'&lt;"wrapper"flipt&gt;'</li>
		 *         <li>'&lt;lf&lt;t&gt;ip&gt;'</li>
		 *       </ul>
		 *     </li>
		 *   </ul>
		 *  @type string
		 *  @default lfrtip <i>(when bJQueryUI is false)</i> <b>or</b> 
		 *    <"H"lfr>t<"F"ip> <i>(when bJQueryUI is true)</i>
		 *  @dtopt Options
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "sDom": '&lt;"top"i&gt;rt&lt;"bottom"flp&gt;&lt;"clear"&gt;'
		 *      } );
		 *    } );
		 */
		"sDom": "lfrtip",
	
	
		/**
		 * DataTables features two different built-in pagination interaction methods
		 * ('two_button' or 'full_numbers') which present different page controls to
		 * the end user. Further methods can be added using the API (see below).
		 *  @type string
		 *  @default two_button
		 *  @dtopt Options
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "sPaginationType": "full_numbers"
		 *      } );
		 *    } )
		 */
		"sPaginationType": "two_button",
	
	
		/**
		 * Enable horizontal scrolling. When a table is too wide to fit into a certain
		 * layout, or you have a large number of columns in the table, you can enable
		 * x-scrolling to show the table in a viewport, which can be scrolled. This
		 * property can be any CSS unit, or a number (in which case it will be treated
		 * as a pixel measurement).
		 *  @type string
		 *  @default <i>blank string - i.e. disabled</i>
		 *  @dtopt Features
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "sScrollX": "100%",
		 *        "bScrollCollapse": true
		 *      } );
		 *    } );
		 */
		"sScrollX": "",
	
	
		/**
		 * This property can be used to force a DataTable to use more width than it
		 * might otherwise do when x-scrolling is enabled. For example if you have a
		 * table which requires to be well spaced, this parameter is useful for
		 * "over-sizing" the table, and thus forcing scrolling. This property can by
		 * any CSS unit, or a number (in which case it will be treated as a pixel
		 * measurement).
		 *  @type string
		 *  @default <i>blank string - i.e. disabled</i>
		 *  @dtopt Options
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "sScrollX": "100%",
		 *        "sScrollXInner": "110%"
		 *      } );
		 *    } );
		 */
		"sScrollXInner": "",
	
	
		/**
		 * Enable vertical scrolling. Vertical scrolling will constrain the DataTable
		 * to the given height, and enable scrolling for any data which overflows the
		 * current viewport. This can be used as an alternative to paging to display
		 * a lot of data in a small area (although paging and scrolling can both be
		 * enabled at the same time). This property can be any CSS unit, or a number
		 * (in which case it will be treated as a pixel measurement).
		 *  @type string
		 *  @default <i>blank string - i.e. disabled</i>
		 *  @dtopt Features
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "sScrollY": "200px",
		 *        "bPaginate": false
		 *      } );
		 *    } );
		 */
		"sScrollY": "",
	
	
		/**
		 * Set the HTTP method that is used to make the Ajax call for server-side
		 * processing or Ajax sourced data.
		 *  @type string
		 *  @default GET
		 *  @dtopt Options
		 *  @dtopt Server-side
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "bServerSide": true,
		 *        "sAjaxSource": "scripts/post.php",
		 *        "sServerMethod": "POST"
		 *      } );
		 *    } );
		 */
		"sServerMethod": "GET"
	};
	
	
	
	/**
	 * Column options that can be given to DataTables at initialisation time.
	 *  @namespace
	 */
	DataTable.defaults.columns = {
		/**
		 * Allows a column's sorting to take multiple columns into account when 
		 * doing a sort. For example first name / last name columns make sense to 
		 * do a multi-column sort over the two columns.
		 *  @type array
		 *  @default null <i>Takes the value of the column index automatically</i>
		 *  @dtopt Columns
		 * 
		 *  @example
		 *    // Using aoColumnDefs
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumnDefs": [
		 *          { "aDataSort": [ 0, 1 ], "aTargets": [ 0 ] },
		 *          { "aDataSort": [ 1, 0 ], "aTargets": [ 1 ] },
		 *          { "aDataSort": [ 2, 3, 4 ], "aTargets": [ 2 ] }
		 *        ]
		 *      } );
		 *    } );
		 *    
		 *  @example
		 *    // Using aoColumns
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumns": [
		 *          { "aDataSort": [ 0, 1 ] },
		 *          { "aDataSort": [ 1, 0 ] },
		 *          { "aDataSort": [ 2, 3, 4 ] },
		 *          null,
		 *          null
		 *        ]
		 *      } );
		 *    } );
		 */
		"aDataSort": null,
	
	
		/**
		 * You can control the default sorting direction, and even alter the behaviour
		 * of the sort handler (i.e. only allow ascending sorting etc) using this
		 * parameter.
		 *  @type array
		 *  @default [ 'asc', 'desc' ]
		 *  @dtopt Columns
		 * 
		 *  @example
		 *    // Using aoColumnDefs
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumnDefs": [
		 *          { "asSorting": [ "asc" ], "aTargets": [ 1 ] },
		 *          { "asSorting": [ "desc", "asc", "asc" ], "aTargets": [ 2 ] },
		 *          { "asSorting": [ "desc" ], "aTargets": [ 3 ] }
		 *        ]
		 *      } );
		 *    } );
		 *    
		 *  @example
		 *    // Using aoColumns
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumns": [
		 *          null,
		 *          { "asSorting": [ "asc" ] },
		 *          { "asSorting": [ "desc", "asc", "asc" ] },
		 *          { "asSorting": [ "desc" ] },
		 *          null
		 *        ]
		 *      } );
		 *    } );
		 */
		"asSorting": [ 'asc', 'desc' ],
	
	
		/**
		 * Enable or disable filtering on the data in this column.
		 *  @type boolean
		 *  @default true
		 *  @dtopt Columns
		 * 
		 *  @example
		 *    // Using aoColumnDefs
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumnDefs": [ 
		 *          { "bSearchable": false, "aTargets": [ 0 ] }
		 *        ] } );
		 *    } );
		 *    
		 *  @example
		 *    // Using aoColumns
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumns": [ 
		 *          { "bSearchable": false },
		 *          null,
		 *          null,
		 *          null,
		 *          null
		 *        ] } );
		 *    } );
		 */
		"bSearchable": true,
	
	
		/**
		 * Enable or disable sorting on this column.
		 *  @type boolean
		 *  @default true
		 *  @dtopt Columns
		 * 
		 *  @example
		 *    // Using aoColumnDefs
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumnDefs": [ 
		 *          { "bSortable": false, "aTargets": [ 0 ] }
		 *        ] } );
		 *    } );
		 *    
		 *  @example
		 *    // Using aoColumns
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumns": [ 
		 *          { "bSortable": false },
		 *          null,
		 *          null,
		 *          null,
		 *          null
		 *        ] } );
		 *    } );
		 */
		"bSortable": true,
	
	
		/**
		 * <code>Deprecated</code> When using fnRender() for a column, you may wish 
		 * to use the original data (before rendering) for sorting and filtering 
		 * (the default is to used the rendered data that the user can see). This 
		 * may be useful for dates etc.
		 * 
		 * Please note that this option has now been deprecated and will be removed
		 * in the next version of DataTables. Please use mRender / mData rather than
		 * fnRender.
		 *  @type boolean
		 *  @default true
		 *  @dtopt Columns
		 *  @deprecated
		 */
		"bUseRendered": true,
	
	
		/**
		 * Enable or disable the display of this column.
		 *  @type boolean
		 *  @default true
		 *  @dtopt Columns
		 * 
		 *  @example
		 *    // Using aoColumnDefs
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumnDefs": [ 
		 *          { "bVisible": false, "aTargets": [ 0 ] }
		 *        ] } );
		 *    } );
		 *    
		 *  @example
		 *    // Using aoColumns
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumns": [ 
		 *          { "bVisible": false },
		 *          null,
		 *          null,
		 *          null,
		 *          null
		 *        ] } );
		 *    } );
		 */
		"bVisible": true,
		
		
		/**
		 * Developer definable function that is called whenever a cell is created (Ajax source,
		 * etc) or processed for input (DOM source). This can be used as a compliment to mRender
		 * allowing you to modify the DOM element (add background colour for example) when the
		 * element is available.
		 *  @type function
		 *  @param {element} nTd The TD node that has been created
		 *  @param {*} sData The Data for the cell
		 *  @param {array|object} oData The data for the whole row
		 *  @param {int} iRow The row index for the aoData data store
		 *  @param {int} iCol The column index for aoColumns
		 *  @dtopt Columns
		 * 
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumnDefs": [ {
		 *          "aTargets": [3],
		 *          "fnCreatedCell": function (nTd, sData, oData, iRow, iCol) {
		 *            if ( sData == "1.7" ) {
		 *              $(nTd).css('color', 'blue')
		 *            }
		 *          }
		 *        } ]
		 *      });
		 *    } );
		 */
		"fnCreatedCell": null,
	
	
		/**
		 * <code>Deprecated</code> Custom display function that will be called for the 
		 * display of each cell in this column.
		 *
		 * Please note that this option has now been deprecated and will be removed
		 * in the next version of DataTables. Please use mRender / mData rather than
		 * fnRender.
		 *  @type function
		 *  @param {object} o Object with the following parameters:
		 *  @param {int}    o.iDataRow The row in aoData
		 *  @param {int}    o.iDataColumn The column in question
		 *  @param {array}  o.aData The data for the row in question
		 *  @param {object} o.oSettings The settings object for this DataTables instance
		 *  @param {object} o.mDataProp The data property used for this column
		 *  @param {*}      val The current cell value
		 *  @returns {string} The string you which to use in the display
		 *  @dtopt Columns
		 *  @deprecated
		 */
		"fnRender": null,
	
	
		/**
		 * The column index (starting from 0!) that you wish a sort to be performed
		 * upon when this column is selected for sorting. This can be used for sorting
		 * on hidden columns for example.
		 *  @type int
		 *  @default -1 <i>Use automatically calculated column index</i>
		 *  @dtopt Columns
		 * 
		 *  @example
		 *    // Using aoColumnDefs
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumnDefs": [ 
		 *          { "iDataSort": 1, "aTargets": [ 0 ] }
		 *        ]
		 *      } );
		 *    } );
		 *    
		 *  @example
		 *    // Using aoColumns
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumns": [ 
		 *          { "iDataSort": 1 },
		 *          null,
		 *          null,
		 *          null,
		 *          null
		 *        ]
		 *      } );
		 *    } );
		 */
		"iDataSort": -1,
	
	
		/**
		 * This parameter has been replaced by mData in DataTables to ensure naming
		 * consistency. mDataProp can still be used, as there is backwards compatibility
		 * in DataTables for this option, but it is strongly recommended that you use
		 * mData in preference to mDataProp.
		 *  @name DataTable.defaults.columns.mDataProp
		 */
	
	
		/**
		 * This property can be used to read data from any JSON data source property,
		 * including deeply nested objects / properties. mData can be given in a
		 * number of different ways which effect its behaviour:
		 *   <ul>
		 *     <li>integer - treated as an array index for the data source. This is the
		 *       default that DataTables uses (incrementally increased for each column).</li>
		 *     <li>string - read an object property from the data source. Note that you can
		 *       use Javascript dotted notation to read deep properties / arrays from the
		 *       data source.</li>
		 *     <li>null - the sDefaultContent option will be used for the cell (null
		 *       by default, so you will need to specify the default content you want -
		 *       typically an empty string). This can be useful on generated columns such 
		 *       as edit / delete action columns.</li>
		 *     <li>function - the function given will be executed whenever DataTables 
		 *       needs to set or get the data for a cell in the column. The function 
		 *       takes three parameters:
		 *       <ul>
		 *         <li>{array|object} The data source for the row</li>
		 *         <li>{string} The type call data requested - this will be 'set' when
		 *           setting data or 'filter', 'display', 'type', 'sort' or undefined when 
		 *           gathering data. Note that when <i>undefined</i> is given for the type
		 *           DataTables expects to get the raw data for the object back</li>
		 *         <li>{*} Data to set when the second parameter is 'set'.</li>
		 *       </ul>
		 *       The return value from the function is not required when 'set' is the type
		 *       of call, but otherwise the return is what will be used for the data
		 *       requested.</li>
		 *    </ul>
		 *
		 * Note that prior to DataTables 1.9.2 mData was called mDataProp. The name change
		 * reflects the flexibility of this property and is consistent with the naming of
		 * mRender. If 'mDataProp' is given, then it will still be used by DataTables, as
		 * it automatically maps the old name to the new if required.
		 *  @type string|int|function|null
		 *  @default null <i>Use automatically calculated column index</i>
		 *  @dtopt Columns
		 * 
		 *  @example
		 *    // Read table data from objects
		 *    $(document).ready( function() {
		 *      var oTable = $('#example').dataTable( {
		 *        "sAjaxSource": "sources/deep.txt",
		 *        "aoColumns": [
		 *          { "mData": "engine" },
		 *          { "mData": "browser" },
		 *          { "mData": "platform.inner" },
		 *          { "mData": "platform.details.0" },
		 *          { "mData": "platform.details.1" }
		 *        ]
		 *      } );
		 *    } );
		 * 
		 *  @example
		 *    // Using mData as a function to provide different information for
		 *    // sorting, filtering and display. In this case, currency (price)
		 *    $(document).ready( function() {
		 *      var oTable = $('#example').dataTable( {
		 *        "aoColumnDefs": [ {
		 *          "aTargets": [ 0 ],
		 *          "mData": function ( source, type, val ) {
		 *            if (type === 'set') {
		 *              source.price = val;
		 *              // Store the computed dislay and filter values for efficiency
		 *              source.price_display = val=="" ? "" : "$"+numberFormat(val);
		 *              source.price_filter  = val=="" ? "" : "$"+numberFormat(val)+" "+val;
		 *              return;
		 *            }
		 *            else if (type === 'display') {
		 *              return source.price_display;
		 *            }
		 *            else if (type === 'filter') {
		 *              return source.price_filter;
		 *            }
		 *            // 'sort', 'type' and undefined all just use the integer
		 *            return source.price;
		 *          }
		 *        } ]
		 *      } );
		 *    } );
		 */
		"mData": null,
	
	
		/**
		 * This property is the rendering partner to mData and it is suggested that
		 * when you want to manipulate data for display (including filtering, sorting etc)
		 * but not altering the underlying data for the table, use this property. mData
		 * can actually do everything this property can and more, but this parameter is
		 * easier to use since there is no 'set' option. Like mData is can be given
		 * in a number of different ways to effect its behaviour, with the addition of 
		 * supporting array syntax for easy outputting of arrays (including arrays of
		 * objects):
		 *   <ul>
		 *     <li>integer - treated as an array index for the data source. This is the
		 *       default that DataTables uses (incrementally increased for each column).</li>
		 *     <li>string - read an object property from the data source. Note that you can
		 *       use Javascript dotted notation to read deep properties / arrays from the
		 *       data source and also array brackets to indicate that the data reader should
		 *       loop over the data source array. When characters are given between the array
		 *       brackets, these characters are used to join the data source array together.
		 *       For example: "accounts[, ].name" would result in a comma separated list with
		 *       the 'name' value from the 'accounts' array of objects.</li>
		 *     <li>function - the function given will be executed whenever DataTables 
		 *       needs to set or get the data for a cell in the column. The function 
		 *       takes three parameters:
		 *       <ul>
		 *         <li>{array|object} The data source for the row (based on mData)</li>
		 *         <li>{string} The type call data requested - this will be 'filter', 'display', 
		 *           'type' or 'sort'.</li>
		 *         <li>{array|object} The full data source for the row (not based on mData)</li>
		 *       </ul>
		 *       The return value from the function is what will be used for the data
		 *       requested.</li>
		 *    </ul>
		 *  @type string|int|function|null
		 *  @default null <i>Use mData</i>
		 *  @dtopt Columns
		 * 
		 *  @example
		 *    // Create a comma separated list from an array of objects
		 *    $(document).ready( function() {
		 *      var oTable = $('#example').dataTable( {
		 *        "sAjaxSource": "sources/deep.txt",
		 *        "aoColumns": [
		 *          { "mData": "engine" },
		 *          { "mData": "browser" },
		 *          {
		 *            "mData": "platform",
		 *            "mRender": "[, ].name"
		 *          }
		 *        ]
		 *      } );
		 *    } );
		 * 
		 *  @example
		 *    // Use as a function to create a link from the data source
		 *    $(document).ready( function() {
		 *      var oTable = $('#example').dataTable( {
		 *        "aoColumnDefs": [
		 *        {
		 *          "aTargets": [ 0 ],
		 *          "mData": "download_link",
		 *          "mRender": function ( data, type, full ) {
		 *            return '<a href="'+data+'">Download</a>';
		 *          }
		 *        ]
		 *      } );
		 *    } );
		 */
		"mRender": null,
	
	
		/**
		 * Change the cell type created for the column - either TD cells or TH cells. This
		 * can be useful as TH cells have semantic meaning in the table body, allowing them
		 * to act as a header for a row (you may wish to add scope='row' to the TH elements).
		 *  @type string
		 *  @default td
		 *  @dtopt Columns
		 * 
		 *  @example
		 *    // Make the first column use TH cells
		 *    $(document).ready( function() {
		 *      var oTable = $('#example').dataTable( {
		 *        "aoColumnDefs": [ {
		 *          "aTargets": [ 0 ],
		 *          "sCellType": "th"
		 *        } ]
		 *      } );
		 *    } );
		 */
		"sCellType": "td",
	
	
		/**
		 * Class to give to each cell in this column.
		 *  @type string
		 *  @default <i>Empty string</i>
		 *  @dtopt Columns
		 * 
		 *  @example
		 *    // Using aoColumnDefs
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumnDefs": [ 
		 *          { "sClass": "my_class", "aTargets": [ 0 ] }
		 *        ]
		 *      } );
		 *    } );
		 *    
		 *  @example
		 *    // Using aoColumns
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumns": [ 
		 *          { "sClass": "my_class" },
		 *          null,
		 *          null,
		 *          null,
		 *          null
		 *        ]
		 *      } );
		 *    } );
		 */
		"sClass": "",
		
		/**
		 * When DataTables calculates the column widths to assign to each column,
		 * it finds the longest string in each column and then constructs a
		 * temporary table and reads the widths from that. The problem with this
		 * is that "mmm" is much wider then "iiii", but the latter is a longer 
		 * string - thus the calculation can go wrong (doing it properly and putting
		 * it into an DOM object and measuring that is horribly(!) slow). Thus as
		 * a "work around" we provide this option. It will append its value to the
		 * text that is found to be the longest string for the column - i.e. padding.
		 * Generally you shouldn't need this, and it is not documented on the 
		 * general DataTables.net documentation
		 *  @type string
		 *  @default <i>Empty string<i>
		 *  @dtopt Columns
		 *    
		 *  @example
		 *    // Using aoColumns
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumns": [ 
		 *          null,
		 *          null,
		 *          null,
		 *          {
		 *            "sContentPadding": "mmm"
		 *          }
		 *        ]
		 *      } );
		 *    } );
		 */
		"sContentPadding": "",
	
	
		/**
		 * Allows a default value to be given for a column's data, and will be used
		 * whenever a null data source is encountered (this can be because mData
		 * is set to null, or because the data source itself is null).
		 *  @type string
		 *  @default null
		 *  @dtopt Columns
		 * 
		 *  @example
		 *    // Using aoColumnDefs
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumnDefs": [ 
		 *          {
		 *            "mData": null,
		 *            "sDefaultContent": "Edit",
		 *            "aTargets": [ -1 ]
		 *          }
		 *        ]
		 *      } );
		 *    } );
		 *    
		 *  @example
		 *    // Using aoColumns
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumns": [ 
		 *          null,
		 *          null,
		 *          null,
		 *          {
		 *            "mData": null,
		 *            "sDefaultContent": "Edit"
		 *          }
		 *        ]
		 *      } );
		 *    } );
		 */
		"sDefaultContent": null,
	
	
		/**
		 * This parameter is only used in DataTables' server-side processing. It can
		 * be exceptionally useful to know what columns are being displayed on the
		 * client side, and to map these to database fields. When defined, the names
		 * also allow DataTables to reorder information from the server if it comes
		 * back in an unexpected order (i.e. if you switch your columns around on the
		 * client-side, your server-side code does not also need updating).
		 *  @type string
		 *  @default <i>Empty string</i>
		 *  @dtopt Columns
		 * 
		 *  @example
		 *    // Using aoColumnDefs
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumnDefs": [ 
		 *          { "sName": "engine", "aTargets": [ 0 ] },
		 *          { "sName": "browser", "aTargets": [ 1 ] },
		 *          { "sName": "platform", "aTargets": [ 2 ] },
		 *          { "sName": "version", "aTargets": [ 3 ] },
		 *          { "sName": "grade", "aTargets": [ 4 ] }
		 *        ]
		 *      } );
		 *    } );
		 *    
		 *  @example
		 *    // Using aoColumns
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumns": [ 
		 *          { "sName": "engine" },
		 *          { "sName": "browser" },
		 *          { "sName": "platform" },
		 *          { "sName": "version" },
		 *          { "sName": "grade" }
		 *        ]
		 *      } );
		 *    } );
		 */
		"sName": "",
	
	
		/**
		 * Defines a data source type for the sorting which can be used to read
		 * real-time information from the table (updating the internally cached
		 * version) prior to sorting. This allows sorting to occur on user editable
		 * elements such as form inputs.
		 *  @type string
		 *  @default std
		 *  @dtopt Columns
		 * 
		 *  @example
		 *    // Using aoColumnDefs
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumnDefs": [
		 *          { "sSortDataType": "dom-text", "aTargets": [ 2, 3 ] },
		 *          { "sType": "numeric", "aTargets": [ 3 ] },
		 *          { "sSortDataType": "dom-select", "aTargets": [ 4 ] },
		 *          { "sSortDataType": "dom-checkbox", "aTargets": [ 5 ] }
		 *        ]
		 *      } );
		 *    } );
		 *    
		 *  @example
		 *    // Using aoColumns
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumns": [
		 *          null,
		 *          null,
		 *          { "sSortDataType": "dom-text" },
		 *          { "sSortDataType": "dom-text", "sType": "numeric" },
		 *          { "sSortDataType": "dom-select" },
		 *          { "sSortDataType": "dom-checkbox" }
		 *        ]
		 *      } );
		 *    } );
		 */
		"sSortDataType": "std",
	
	
		/**
		 * The title of this column.
		 *  @type string
		 *  @default null <i>Derived from the 'TH' value for this column in the 
		 *    original HTML table.</i>
		 *  @dtopt Columns
		 * 
		 *  @example
		 *    // Using aoColumnDefs
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumnDefs": [ 
		 *          { "sTitle": "My column title", "aTargets": [ 0 ] }
		 *        ]
		 *      } );
		 *    } );
		 *    
		 *  @example
		 *    // Using aoColumns
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumns": [ 
		 *          { "sTitle": "My column title" },
		 *          null,
		 *          null,
		 *          null,
		 *          null
		 *        ]
		 *      } );
		 *    } );
		 */
		"sTitle": null,
	
	
		/**
		 * The type allows you to specify how the data for this column will be sorted.
		 * Four types (string, numeric, date and html (which will strip HTML tags
		 * before sorting)) are currently available. Note that only date formats
		 * understood by Javascript's Date() object will be accepted as type date. For
		 * example: "Mar 26, 2008 5:03 PM". May take the values: 'string', 'numeric',
		 * 'date' or 'html' (by default). Further types can be adding through
		 * plug-ins.
		 *  @type string
		 *  @default null <i>Auto-detected from raw data</i>
		 *  @dtopt Columns
		 * 
		 *  @example
		 *    // Using aoColumnDefs
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumnDefs": [ 
		 *          { "sType": "html", "aTargets": [ 0 ] }
		 *        ]
		 *      } );
		 *    } );
		 *    
		 *  @example
		 *    // Using aoColumns
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumns": [ 
		 *          { "sType": "html" },
		 *          null,
		 *          null,
		 *          null,
		 *          null
		 *        ]
		 *      } );
		 *    } );
		 */
		"sType": null,
	
	
		/**
		 * Defining the width of the column, this parameter may take any CSS value
		 * (3em, 20px etc). DataTables apples 'smart' widths to columns which have not
		 * been given a specific width through this interface ensuring that the table
		 * remains readable.
		 *  @type string
		 *  @default null <i>Automatic</i>
		 *  @dtopt Columns
		 * 
		 *  @example
		 *    // Using aoColumnDefs
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumnDefs": [ 
		 *          { "sWidth": "20%", "aTargets": [ 0 ] }
		 *        ]
		 *      } );
		 *    } );
		 *    
		 *  @example
		 *    // Using aoColumns
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "aoColumns": [ 
		 *          { "sWidth": "20%" },
		 *          null,
		 *          null,
		 *          null,
		 *          null
		 *        ]
		 *      } );
		 *    } );
		 */
		"sWidth": null
	};
	
	
	
	/**
	 * DataTables settings object - this holds all the information needed for a
	 * given table, including configuration, data and current application of the
	 * table options. DataTables does not have a single instance for each DataTable
	 * with the settings attached to that instance, but rather instances of the
	 * DataTable "class" are created on-the-fly as needed (typically by a 
	 * $().dataTable() call) and the settings object is then applied to that
	 * instance.
	 * 
	 * Note that this object is related to {@link DataTable.defaults} but this 
	 * one is the internal data store for DataTables's cache of columns. It should
	 * NOT be manipulated outside of DataTables. Any configuration should be done
	 * through the initialisation options.
	 *  @namespace
	 *  @todo Really should attach the settings object to individual instances so we
	 *    don't need to create new instances on each $().dataTable() call (if the
	 *    table already exists). It would also save passing oSettings around and
	 *    into every single function. However, this is a very significant 
	 *    architecture change for DataTables and will almost certainly break
	 *    backwards compatibility with older installations. This is something that
	 *    will be done in 2.0.
	 */
	DataTable.models.oSettings = {
		/**
		 * Primary features of DataTables and their enablement state.
		 *  @namespace
		 */
		"oFeatures": {
			
			/**
			 * Flag to say if DataTables should automatically try to calculate the
			 * optimum table and columns widths (true) or not (false).
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bAutoWidth": null,
	
			/**
			 * Delay the creation of TR and TD elements until they are actually
			 * needed by a driven page draw. This can give a significant speed
			 * increase for Ajax source and Javascript source data, but makes no
			 * difference at all fro DOM and server-side processing tables.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bDeferRender": null,
			
			/**
			 * Enable filtering on the table or not. Note that if this is disabled
			 * then there is no filtering at all on the table, including fnFilter.
			 * To just remove the filtering input use sDom and remove the 'f' option.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bFilter": null,
			
			/**
			 * Table information element (the 'Showing x of y records' div) enable
			 * flag.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bInfo": null,
			
			/**
			 * Present a user control allowing the end user to change the page size
			 * when pagination is enabled.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bLengthChange": null,
	
			/**
			 * Pagination enabled or not. Note that if this is disabled then length
			 * changing must also be disabled.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bPaginate": null,
			
			/**
			 * Processing indicator enable flag whenever DataTables is enacting a
			 * user request - typically an Ajax request for server-side processing.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bProcessing": null,
			
			/**
			 * Server-side processing enabled flag - when enabled DataTables will
			 * get all data from the server for every draw - there is no filtering,
			 * sorting or paging done on the client-side.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bServerSide": null,
			
			/**
			 * Sorting enablement flag.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bSort": null,
			
			/**
			 * Apply a class to the columns which are being sorted to provide a
			 * visual highlight or not. This can slow things down when enabled since
			 * there is a lot of DOM interaction.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bSortClasses": null,
			
			/**
			 * State saving enablement flag.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bStateSave": null
		},
		
	
		/**
		 * Scrolling settings for a table.
		 *  @namespace
		 */
		"oScroll": {
			/**
			 * Indicate if DataTables should be allowed to set the padding / margin
			 * etc for the scrolling header elements or not. Typically you will want
			 * this.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bAutoCss": null,
			
			/**
			 * When the table is shorter in height than sScrollY, collapse the
			 * table container down to the height of the table (when true).
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bCollapse": null,
			
			/**
			 * Infinite scrolling enablement flag. Now deprecated in favour of
			 * using the Scroller plug-in.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bInfinite": null,
			
			/**
			 * Width of the scrollbar for the web-browser's platform. Calculated
			 * during table initialisation.
			 *  @type int
			 *  @default 0
			 */
			"iBarWidth": 0,
			
			/**
			 * Space (in pixels) between the bottom of the scrolling container and 
			 * the bottom of the scrolling viewport before the next page is loaded
			 * when using infinite scrolling.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type int
			 */
			"iLoadGap": null,
			
			/**
			 * Viewport width for horizontal scrolling. Horizontal scrolling is 
			 * disabled if an empty string.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type string
			 */
			"sX": null,
			
			/**
			 * Width to expand the table to when using x-scrolling. Typically you
			 * should not need to use this.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type string
			 *  @deprecated
			 */
			"sXInner": null,
			
			/**
			 * Viewport height for vertical scrolling. Vertical scrolling is disabled
			 * if an empty string.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type string
			 */
			"sY": null
		},
		
		/**
		 * Language information for the table.
		 *  @namespace
		 *  @extends DataTable.defaults.oLanguage
		 */
		"oLanguage": {
			/**
			 * Information callback function. See 
			 * {@link DataTable.defaults.fnInfoCallback}
			 *  @type function
			 *  @default null
			 */
			"fnInfoCallback": null
		},
		
		/**
		 * Browser support parameters
		 *  @namespace
		 */
		"oBrowser": {
			/**
			 * Indicate if the browser incorrectly calculates width:100% inside a
			 * scrolling element (IE6/7)
			 *  @type boolean
			 *  @default false
			 */
			"bScrollOversize": false
		},
		
		/**
		 * Array referencing the nodes which are used for the features. The 
		 * parameters of this object match what is allowed by sDom - i.e.
		 *   <ul>
		 *     <li>'l' - Length changing</li>
		 *     <li>'f' - Filtering input</li>
		 *     <li>'t' - The table!</li>
		 *     <li>'i' - Information</li>
		 *     <li>'p' - Pagination</li>
		 *     <li>'r' - pRocessing</li>
		 *   </ul>
		 *  @type array
		 *  @default []
		 */
		"aanFeatures": [],
		
		/**
		 * Store data information - see {@link DataTable.models.oRow} for detailed
		 * information.
		 *  @type array
		 *  @default []
		 */
		"aoData": [],
		
		/**
		 * Array of indexes which are in the current display (after filtering etc)
		 *  @type array
		 *  @default []
		 */
		"aiDisplay": [],
		
		/**
		 * Array of indexes for display - no filtering
		 *  @type array
		 *  @default []
		 */
		"aiDisplayMaster": [],
		
		/**
		 * Store information about each column that is in use
		 *  @type array
		 *  @default []
		 */
		"aoColumns": [],
		
		/**
		 * Store information about the table's header
		 *  @type array
		 *  @default []
		 */
		"aoHeader": [],
		
		/**
		 * Store information about the table's footer
		 *  @type array
		 *  @default []
		 */
		"aoFooter": [],
		
		/**
		 * Search data array for regular expression searching
		 *  @type array
		 *  @default []
		 */
		"asDataSearch": [],
		
		/**
		 * Store the applied global search information in case we want to force a 
		 * research or compare the old search to a new one.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @namespace
		 *  @extends DataTable.models.oSearch
		 */
		"oPreviousSearch": {},
		
		/**
		 * Store the applied search for each column - see 
		 * {@link DataTable.models.oSearch} for the format that is used for the
		 * filtering information for each column.
		 *  @type array
		 *  @default []
		 */
		"aoPreSearchCols": [],
		
		/**
		 * Sorting that is applied to the table. Note that the inner arrays are
		 * used in the following manner:
		 * <ul>
		 *   <li>Index 0 - column number</li>
		 *   <li>Index 1 - current sorting direction</li>
		 *   <li>Index 2 - index of asSorting for this column</li>
		 * </ul>
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type array
		 *  @todo These inner arrays should really be objects
		 */
		"aaSorting": null,
		
		/**
		 * Sorting that is always applied to the table (i.e. prefixed in front of
		 * aaSorting).
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type array|null
		 *  @default null
		 */
		"aaSortingFixed": null,
		
		/**
		 * Classes to use for the striping of a table.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type array
		 *  @default []
		 */
		"asStripeClasses": null,
		
		/**
		 * If restoring a table - we should restore its striping classes as well
		 *  @type array
		 *  @default []
		 */
		"asDestroyStripes": [],
		
		/**
		 * If restoring a table - we should restore its width 
		 *  @type int
		 *  @default 0
		 */
		"sDestroyWidth": 0,
		
		/**
		 * Callback functions array for every time a row is inserted (i.e. on a draw).
		 *  @type array
		 *  @default []
		 */
		"aoRowCallback": [],
		
		/**
		 * Callback functions for the header on each draw.
		 *  @type array
		 *  @default []
		 */
		"aoHeaderCallback": [],
		
		/**
		 * Callback function for the footer on each draw.
		 *  @type array
		 *  @default []
		 */
		"aoFooterCallback": [],
		
		/**
		 * Array of callback functions for draw callback functions
		 *  @type array
		 *  @default []
		 */
		"aoDrawCallback": [],
		
		/**
		 * Array of callback functions for row created function
		 *  @type array
		 *  @default []
		 */
		"aoRowCreatedCallback": [],
		
		/**
		 * Callback functions for just before the table is redrawn. A return of 
		 * false will be used to cancel the draw.
		 *  @type array
		 *  @default []
		 */
		"aoPreDrawCallback": [],
		
		/**
		 * Callback functions for when the table has been initialised.
		 *  @type array
		 *  @default []
		 */
		"aoInitComplete": [],
	
		
		/**
		 * Callbacks for modifying the settings to be stored for state saving, prior to
		 * saving state.
		 *  @type array
		 *  @default []
		 */
		"aoStateSaveParams": [],
		
		/**
		 * Callbacks for modifying the settings that have been stored for state saving
		 * prior to using the stored values to restore the state.
		 *  @type array
		 *  @default []
		 */
		"aoStateLoadParams": [],
		
		/**
		 * Callbacks for operating on the settings object once the saved state has been
		 * loaded
		 *  @type array
		 *  @default []
		 */
		"aoStateLoaded": [],
		
		/**
		 * Cache the table ID for quick access
		 *  @type string
		 *  @default <i>Empty string</i>
		 */
		"sTableId": "",
		
		/**
		 * The TABLE node for the main table
		 *  @type node
		 *  @default null
		 */
		"nTable": null,
		
		/**
		 * Permanent ref to the thead element
		 *  @type node
		 *  @default null
		 */
		"nTHead": null,
		
		/**
		 * Permanent ref to the tfoot element - if it exists
		 *  @type node
		 *  @default null
		 */
		"nTFoot": null,
		
		/**
		 * Permanent ref to the tbody element
		 *  @type node
		 *  @default null
		 */
		"nTBody": null,
		
		/**
		 * Cache the wrapper node (contains all DataTables controlled elements)
		 *  @type node
		 *  @default null
		 */
		"nTableWrapper": null,
		
		/**
		 * Indicate if when using server-side processing the loading of data 
		 * should be deferred until the second draw.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type boolean
		 *  @default false
		 */
		"bDeferLoading": false,
		
		/**
		 * Indicate if all required information has been read in
		 *  @type boolean
		 *  @default false
		 */
		"bInitialised": false,
		
		/**
		 * Information about open rows. Each object in the array has the parameters
		 * 'nTr' and 'nParent'
		 *  @type array
		 *  @default []
		 */
		"aoOpenRows": [],
		
		/**
		 * Dictate the positioning of DataTables' control elements - see
		 * {@link DataTable.model.oInit.sDom}.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type string
		 *  @default null
		 */
		"sDom": null,
		
		/**
		 * Which type of pagination should be used.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type string 
		 *  @default two_button
		 */
		"sPaginationType": "two_button",
		
		/**
		 * The cookie duration (for bStateSave) in seconds.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type int
		 *  @default 0
		 */
		"iCookieDuration": 0,
		
		/**
		 * The cookie name prefix.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type string
		 *  @default <i>Empty string</i>
		 */
		"sCookiePrefix": "",
		
		/**
		 * Callback function for cookie creation.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type function
		 *  @default null
		 */
		"fnCookieCallback": null,
		
		/**
		 * Array of callback functions for state saving. Each array element is an 
		 * object with the following parameters:
		 *   <ul>
		 *     <li>function:fn - function to call. Takes two parameters, oSettings
		 *       and the JSON string to save that has been thus far created. Returns
		 *       a JSON string to be inserted into a json object 
		 *       (i.e. '"param": [ 0, 1, 2]')</li>
		 *     <li>string:sName - name of callback</li>
		 *   </ul>
		 *  @type array
		 *  @default []
		 */
		"aoStateSave": [],
		
		/**
		 * Array of callback functions for state loading. Each array element is an 
		 * object with the following parameters:
		 *   <ul>
		 *     <li>function:fn - function to call. Takes two parameters, oSettings 
		 *       and the object stored. May return false to cancel state loading</li>
		 *     <li>string:sName - name of callback</li>
		 *   </ul>
		 *  @type array
		 *  @default []
		 */
		"aoStateLoad": [],
		
		/**
		 * State that was loaded from the cookie. Useful for back reference
		 *  @type object
		 *  @default null
		 */
		"oLoadedState": null,
		
		/**
		 * Source url for AJAX data for the table.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type string
		 *  @default null
		 */
		"sAjaxSource": null,
		
		/**
		 * Property from a given object from which to read the table data from. This
		 * can be an empty string (when not server-side processing), in which case 
		 * it is  assumed an an array is given directly.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type string
		 */
		"sAjaxDataProp": null,
		
		/**
		 * Note if draw should be blocked while getting data
		 *  @type boolean
		 *  @default true
		 */
		"bAjaxDataGet": true,
		
		/**
		 * The last jQuery XHR object that was used for server-side data gathering. 
		 * This can be used for working with the XHR information in one of the 
		 * callbacks
		 *  @type object
		 *  @default null
		 */
		"jqXHR": null,
		
		/**
		 * Function to get the server-side data.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type function
		 */
		"fnServerData": null,
		
		/**
		 * Functions which are called prior to sending an Ajax request so extra 
		 * parameters can easily be sent to the server
		 *  @type array
		 *  @default []
		 */
		"aoServerParams": [],
		
		/**
		 * Send the XHR HTTP method - GET or POST (could be PUT or DELETE if 
		 * required).
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type string
		 */
		"sServerMethod": null,
		
		/**
		 * Format numbers for display.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type function
		 */
		"fnFormatNumber": null,
		
		/**
		 * List of options that can be used for the user selectable length menu.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type array
		 *  @default []
		 */
		"aLengthMenu": null,
		
		/**
		 * Counter for the draws that the table does. Also used as a tracker for
		 * server-side processing
		 *  @type int
		 *  @default 0
		 */
		"iDraw": 0,
		
		/**
		 * Indicate if a redraw is being done - useful for Ajax
		 *  @type boolean
		 *  @default false
		 */
		"bDrawing": false,
		
		/**
		 * Draw index (iDraw) of the last error when parsing the returned data
		 *  @type int
		 *  @default -1
		 */
		"iDrawError": -1,
		
		/**
		 * Paging display length
		 *  @type int
		 *  @default 10
		 */
		"_iDisplayLength": 10,
	
		/**
		 * Paging start point - aiDisplay index
		 *  @type int
		 *  @default 0
		 */
		"_iDisplayStart": 0,
	
		/**
		 * Paging end point - aiDisplay index. Use fnDisplayEnd rather than
		 * this property to get the end point
		 *  @type int
		 *  @default 10
		 *  @private
		 */
		"_iDisplayEnd": 10,
		
		/**
		 * Server-side processing - number of records in the result set
		 * (i.e. before filtering), Use fnRecordsTotal rather than
		 * this property to get the value of the number of records, regardless of
		 * the server-side processing setting.
		 *  @type int
		 *  @default 0
		 *  @private
		 */
		"_iRecordsTotal": 0,
	
		/**
		 * Server-side processing - number of records in the current display set
		 * (i.e. after filtering). Use fnRecordsDisplay rather than
		 * this property to get the value of the number of records, regardless of
		 * the server-side processing setting.
		 *  @type boolean
		 *  @default 0
		 *  @private
		 */
		"_iRecordsDisplay": 0,
		
		/**
		 * Flag to indicate if jQuery UI marking and classes should be used.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type boolean
		 */
		"bJUI": null,
		
		/**
		 * The classes to use for the table
		 *  @type object
		 *  @default {}
		 */
		"oClasses": {},
		
		/**
		 * Flag attached to the settings object so you can check in the draw 
		 * callback if filtering has been done in the draw. Deprecated in favour of
		 * events.
		 *  @type boolean
		 *  @default false
		 *  @deprecated
		 */
		"bFiltered": false,
		
		/**
		 * Flag attached to the settings object so you can check in the draw 
		 * callback if sorting has been done in the draw. Deprecated in favour of
		 * events.
		 *  @type boolean
		 *  @default false
		 *  @deprecated
		 */
		"bSorted": false,
		
		/**
		 * Indicate that if multiple rows are in the header and there is more than 
		 * one unique cell per column, if the top one (true) or bottom one (false) 
		 * should be used for sorting / title by DataTables.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type boolean
		 */
		"bSortCellsTop": null,
		
		/**
		 * Initialisation object that is used for the table
		 *  @type object
		 *  @default null
		 */
		"oInit": null,
		
		/**
		 * Destroy callback functions - for plug-ins to attach themselves to the
		 * destroy so they can clean up markup and events.
		 *  @type array
		 *  @default []
		 */
		"aoDestroyCallback": [],
	
		
		/**
		 * Get the number of records in the current record set, before filtering
		 *  @type function
		 */
		"fnRecordsTotal": function ()
		{
			if ( this.oFeatures.bServerSide ) {
				return parseInt(this._iRecordsTotal, 10);
			} else {
				return this.aiDisplayMaster.length;
			}
		},
		
		/**
		 * Get the number of records in the current record set, after filtering
		 *  @type function
		 */
		"fnRecordsDisplay": function ()
		{
			if ( this.oFeatures.bServerSide ) {
				return parseInt(this._iRecordsDisplay, 10);
			} else {
				return this.aiDisplay.length;
			}
		},
		
		/**
		 * Set the display end point - aiDisplay index
		 *  @type function
		 *  @todo Should do away with _iDisplayEnd and calculate it on-the-fly here
		 */
		"fnDisplayEnd": function ()
		{
			if ( this.oFeatures.bServerSide ) {
				if ( this.oFeatures.bPaginate === false || this._iDisplayLength == -1 ) {
					return this._iDisplayStart+this.aiDisplay.length;
				} else {
					return Math.min( this._iDisplayStart+this._iDisplayLength, 
						this._iRecordsDisplay );
				}
			} else {
				return this._iDisplayEnd;
			}
		},
		
		/**
		 * The DataTables object for this table
		 *  @type object
		 *  @default null
		 */
		"oInstance": null,
		
		/**
		 * Unique identifier for each instance of the DataTables object. If there
		 * is an ID on the table node, then it takes that value, otherwise an
		 * incrementing internal counter is used.
		 *  @type string
		 *  @default null
		 */
		"sInstance": null,
	
		/**
		 * tabindex attribute value that is added to DataTables control elements, allowing
		 * keyboard navigation of the table and its controls.
		 */
		"iTabIndex": 0,
	
		/**
		 * DIV container for the footer scrolling table if scrolling
		 */
		"nScrollHead": null,
	
		/**
		 * DIV container for the footer scrolling table if scrolling
		 */
		"nScrollFoot": null
	};

	/**
	 * Extension object for DataTables that is used to provide all extension options.
	 * 
	 * Note that the <i>DataTable.ext</i> object is available through
	 * <i>jQuery.fn.dataTable.ext</i> where it may be accessed and manipulated. It is
	 * also aliased to <i>jQuery.fn.dataTableExt</i> for historic reasons.
	 *  @namespace
	 *  @extends DataTable.models.ext
	 */
	DataTable.ext = $.extend( true, {}, DataTable.models.ext );
	
	$.extend( DataTable.ext.oStdClasses, {
		"sTable": "dataTable",
	
		/* Two buttons buttons */
		"sPagePrevEnabled": "paginate_enabled_previous",
		"sPagePrevDisabled": "paginate_disabled_previous",
		"sPageNextEnabled": "paginate_enabled_next",
		"sPageNextDisabled": "paginate_disabled_next",
		"sPageJUINext": "",
		"sPageJUIPrev": "",
		
		/* Full numbers paging buttons */
		"sPageButton": "paginate_button",
		"sPageButtonActive": "paginate_active",
		"sPageButtonStaticDisabled": "paginate_button paginate_button_disabled",
		"sPageFirst": "first",
		"sPagePrevious": "previous",
		"sPageNext": "next",
		"sPageLast": "last",
		
		/* Striping classes */
		"sStripeOdd": "odd",
		"sStripeEven": "even",
		
		/* Empty row */
		"sRowEmpty": "dataTables_empty",
		
		/* Features */
		"sWrapper": "dataTables_wrapper",
		"sFilter": "dataTables_filter",
		"sInfo": "dataTables_info",
		"sPaging": "dataTables_paginate paging_", /* Note that the type is postfixed */
		"sLength": "dataTables_length",
		"sProcessing": "dataTables_processing",
		
		/* Sorting */
		"sSortAsc": "sorting_asc",
		"sSortDesc": "sorting_desc",
		"sSortable": "sorting", /* Sortable in both directions */
		"sSortableAsc": "sorting_asc_disabled",
		"sSortableDesc": "sorting_desc_disabled",
		"sSortableNone": "sorting_disabled",
		"sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */
		"sSortJUIAsc": "",
		"sSortJUIDesc": "",
		"sSortJUI": "",
		"sSortJUIAscAllowed": "",
		"sSortJUIDescAllowed": "",
		"sSortJUIWrapper": "",
		"sSortIcon": "",
		
		/* Scrolling */
		"sScrollWrapper": "dataTables_scroll",
		"sScrollHead": "dataTables_scrollHead",
		"sScrollHeadInner": "dataTables_scrollHeadInner",
		"sScrollBody": "dataTables_scrollBody",
		"sScrollFoot": "dataTables_scrollFoot",
		"sScrollFootInner": "dataTables_scrollFootInner",
		
		/* Misc */
		"sFooterTH": "",
		"sJUIHeader": "",
		"sJUIFooter": ""
	} );
	
	
	$.extend( DataTable.ext.oJUIClasses, DataTable.ext.oStdClasses, {
		/* Two buttons buttons */
		"sPagePrevEnabled": "fg-button ui-button ui-state-default ui-corner-left",
		"sPagePrevDisabled": "fg-button ui-button ui-state-default ui-corner-left ui-state-disabled",
		"sPageNextEnabled": "fg-button ui-button ui-state-default ui-corner-right",
		"sPageNextDisabled": "fg-button ui-button ui-state-default ui-corner-right ui-state-disabled",
		"sPageJUINext": "ui-icon ui-icon-circle-arrow-e",
		"sPageJUIPrev": "ui-icon ui-icon-circle-arrow-w",
		
		/* Full numbers paging buttons */
		"sPageButton": "fg-button ui-button ui-state-default",
		"sPageButtonActive": "fg-button ui-button ui-state-default ui-state-disabled",
		"sPageButtonStaticDisabled": "fg-button ui-button ui-state-default ui-state-disabled",
		"sPageFirst": "first ui-corner-tl ui-corner-bl",
		"sPageLast": "last ui-corner-tr ui-corner-br",
		
		/* Features */
		"sPaging": "dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi "+
			"ui-buttonset-multi paging_", /* Note that the type is postfixed */
		
		/* Sorting */
		"sSortAsc": "ui-state-default",
		"sSortDesc": "ui-state-default",
		"sSortable": "ui-state-default",
		"sSortableAsc": "ui-state-default",
		"sSortableDesc": "ui-state-default",
		"sSortableNone": "ui-state-default",
		"sSortJUIAsc": "css_right ui-icon ui-icon-triangle-1-n",
		"sSortJUIDesc": "css_right ui-icon ui-icon-triangle-1-s",
		"sSortJUI": "css_right ui-icon ui-icon-carat-2-n-s",
		"sSortJUIAscAllowed": "css_right ui-icon ui-icon-carat-1-n",
		"sSortJUIDescAllowed": "css_right ui-icon ui-icon-carat-1-s",
		"sSortJUIWrapper": "DataTables_sort_wrapper",
		"sSortIcon": "DataTables_sort_icon",
		
		/* Scrolling */
		"sScrollHead": "dataTables_scrollHead ui-state-default",
		"sScrollFoot": "dataTables_scrollFoot ui-state-default",
		
		/* Misc */
		"sFooterTH": "ui-state-default",
		"sJUIHeader": "fg-toolbar ui-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix",
		"sJUIFooter": "fg-toolbar ui-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix"
	} );
	
	/*
	 * Variable: oPagination
	 * Purpose:  
	 * Scope:    jQuery.fn.dataTableExt
	 */
	$.extend( DataTable.ext.oPagination, {
		/*
		 * Variable: two_button
		 * Purpose:  Standard two button (forward/back) pagination
		 * Scope:    jQuery.fn.dataTableExt.oPagination
		 */
		"two_button": {
			/*
			 * Function: oPagination.two_button.fnInit
			 * Purpose:  Initialise dom elements required for pagination with forward/back buttons only
			 * Returns:  -
			 * Inputs:   object:oSettings - dataTables settings object
			 *           node:nPaging - the DIV which contains this pagination control
			 *           function:fnCallbackDraw - draw function which must be called on update
			 */
			"fnInit": function ( oSettings, nPaging, fnCallbackDraw )
			{
				var oLang = oSettings.oLanguage.oPaginate;
				var oClasses = oSettings.oClasses;
				var fnClickHandler = function ( e ) {
					if ( oSettings.oApi._fnPageChange( oSettings, e.data.action ) )
					{
						fnCallbackDraw( oSettings );
					}
				};
	
				var sAppend = (!oSettings.bJUI) ?
					'<a class="'+oSettings.oClasses.sPagePrevDisabled+'" tabindex="'+oSettings.iTabIndex+'" role="button">'+oLang.sPrevious+'</a>'+
					'<a class="'+oSettings.oClasses.sPageNextDisabled+'" tabindex="'+oSettings.iTabIndex+'" role="button">'+oLang.sNext+'</a>'
					:
					'<a class="'+oSettings.oClasses.sPagePrevDisabled+'" tabindex="'+oSettings.iTabIndex+'" role="button"><span class="'+oSettings.oClasses.sPageJUIPrev+'"></span></a>'+
					'<a class="'+oSettings.oClasses.sPageNextDisabled+'" tabindex="'+oSettings.iTabIndex+'" role="button"><span class="'+oSettings.oClasses.sPageJUINext+'"></span></a>';
				$(nPaging).append( sAppend );
				
				var els = $('a', nPaging);
				var nPrevious = els[0],
					nNext = els[1];
				
				oSettings.oApi._fnBindAction( nPrevious, {action: "previous"}, fnClickHandler );
				oSettings.oApi._fnBindAction( nNext,     {action: "next"},     fnClickHandler );
				
				/* ID the first elements only */
				if ( !oSettings.aanFeatures.p )
				{
					nPaging.id = oSettings.sTableId+'_paginate';
					nPrevious.id = oSettings.sTableId+'_previous';
					nNext.id = oSettings.sTableId+'_next';
	
					nPrevious.setAttribute('aria-controls', oSettings.sTableId);
					nNext.setAttribute('aria-controls', oSettings.sTableId);
				}
			},
			
			/*
			 * Function: oPagination.two_button.fnUpdate
			 * Purpose:  Update the two button pagination at the end of the draw
			 * Returns:  -
			 * Inputs:   object:oSettings - dataTables settings object
			 *           function:fnCallbackDraw - draw function to call on page change
			 */
			"fnUpdate": function ( oSettings, fnCallbackDraw )
			{
				if ( !oSettings.aanFeatures.p )
				{
					return;
				}
				
				var oClasses = oSettings.oClasses;
				var an = oSettings.aanFeatures.p;
				var nNode;
	
				/* Loop over each instance of the pager */
				for ( var i=0, iLen=an.length ; i<iLen ; i++ )
				{
					nNode = an[i].firstChild;
					if ( nNode )
					{
						/* Previous page */
						nNode.className = ( oSettings._iDisplayStart === 0 ) ?
						    oClasses.sPagePrevDisabled : oClasses.sPagePrevEnabled;
						    
						/* Next page */
						nNode = nNode.nextSibling;
						nNode.className = ( oSettings.fnDisplayEnd() == oSettings.fnRecordsDisplay() ) ?
						    oClasses.sPageNextDisabled : oClasses.sPageNextEnabled;
					}
				}
			}
		},
		
		
		/*
		 * Variable: iFullNumbersShowPages
		 * Purpose:  Change the number of pages which can be seen
		 * Scope:    jQuery.fn.dataTableExt.oPagination
		 */
		"iFullNumbersShowPages": 5,
		
		/*
		 * Variable: full_numbers
		 * Purpose:  Full numbers pagination
		 * Scope:    jQuery.fn.dataTableExt.oPagination
		 */
		"full_numbers": {
			/*
			 * Function: oPagination.full_numbers.fnInit
			 * Purpose:  Initialise dom elements required for pagination with a list of the pages
			 * Returns:  -
			 * Inputs:   object:oSettings - dataTables settings object
			 *           node:nPaging - the DIV which contains this pagination control
			 *           function:fnCallbackDraw - draw function which must be called on update
			 */
			"fnInit": function ( oSettings, nPaging, fnCallbackDraw )
			{
				var oLang = oSettings.oLanguage.oPaginate;
				var oClasses = oSettings.oClasses;
				var fnClickHandler = function ( e ) {
					if ( oSettings.oApi._fnPageChange( oSettings, e.data.action ) )
					{
						fnCallbackDraw( oSettings );
					}
				};
	
				$(nPaging).append(
					'<a  tabindex="'+oSettings.iTabIndex+'" class="'+oClasses.sPageButton+" "+oClasses.sPageFirst+'">'+oLang.sFirst+'</a>'+
					'<a  tabindex="'+oSettings.iTabIndex+'" class="'+oClasses.sPageButton+" "+oClasses.sPagePrevious+'">'+oLang.sPrevious+'</a>'+
					'<span></span>'+
					'<a tabindex="'+oSettings.iTabIndex+'" class="'+oClasses.sPageButton+" "+oClasses.sPageNext+'">'+oLang.sNext+'</a>'+
					'<a tabindex="'+oSettings.iTabIndex+'" class="'+oClasses.sPageButton+" "+oClasses.sPageLast+'">'+oLang.sLast+'</a>'
				);
				var els = $('a', nPaging);
				var nFirst = els[0],
					nPrev = els[1],
					nNext = els[2],
					nLast = els[3];
				
				oSettings.oApi._fnBindAction( nFirst, {action: "first"},    fnClickHandler );
				oSettings.oApi._fnBindAction( nPrev,  {action: "previous"}, fnClickHandler );
				oSettings.oApi._fnBindAction( nNext,  {action: "next"},     fnClickHandler );
				oSettings.oApi._fnBindAction( nLast,  {action: "last"},     fnClickHandler );
				
				/* ID the first elements only */
				if ( !oSettings.aanFeatures.p )
				{
					nPaging.id = oSettings.sTableId+'_paginate';
					nFirst.id =oSettings.sTableId+'_first';
					nPrev.id =oSettings.sTableId+'_previous';
					nNext.id =oSettings.sTableId+'_next';
					nLast.id =oSettings.sTableId+'_last';
				}
			},
			
			/*
			 * Function: oPagination.full_numbers.fnUpdate
			 * Purpose:  Update the list of page buttons shows
			 * Returns:  -
			 * Inputs:   object:oSettings - dataTables settings object
			 *           function:fnCallbackDraw - draw function to call on page change
			 */
			"fnUpdate": function ( oSettings, fnCallbackDraw )
			{
				if ( !oSettings.aanFeatures.p )
				{
					return;
				}
				
				var iPageCount = DataTable.ext.oPagination.iFullNumbersShowPages;
				var iPageCountHalf = Math.floor(iPageCount / 2);
				var iPages = Math.ceil((oSettings.fnRecordsDisplay()) / oSettings._iDisplayLength);
				var iCurrentPage = Math.ceil(oSettings._iDisplayStart / oSettings._iDisplayLength) + 1;
				var sList = "";
				var iStartButton, iEndButton, i, iLen;
				var oClasses = oSettings.oClasses;
				var anButtons, anStatic, nPaginateList, nNode;
				var an = oSettings.aanFeatures.p;
				var fnBind = function (j) {
					oSettings.oApi._fnBindAction( this, {"page": j+iStartButton-1}, function(e) {
						/* Use the information in the element to jump to the required page */
						oSettings.oApi._fnPageChange( oSettings, e.data.page );
						fnCallbackDraw( oSettings );
						e.preventDefault();
					} );
				};
				
				/* Pages calculation */
				if ( oSettings._iDisplayLength === -1 )
				{
					iStartButton = 1;
					iEndButton = 1;
					iCurrentPage = 1;
				}
				else if (iPages < iPageCount)
				{
					iStartButton = 1;
					iEndButton = iPages;
				}
				else if (iCurrentPage <= iPageCountHalf)
				{
					iStartButton = 1;
					iEndButton = iPageCount;
				}
				else if (iCurrentPage >= (iPages - iPageCountHalf))
				{
					iStartButton = iPages - iPageCount + 1;
					iEndButton = iPages;
				}
				else
				{
					iStartButton = iCurrentPage - Math.ceil(iPageCount / 2) + 1;
					iEndButton = iStartButton + iPageCount - 1;
				}
	
				
				/* Build the dynamic list */
				for ( i=iStartButton ; i<=iEndButton ; i++ )
				{
					sList += (iCurrentPage !== i) ?
						'<a tabindex="'+oSettings.iTabIndex+'" class="'+oClasses.sPageButton+'">'+oSettings.fnFormatNumber(i)+'</a>' :
						'<a tabindex="'+oSettings.iTabIndex+'" class="'+oClasses.sPageButtonActive+'">'+oSettings.fnFormatNumber(i)+'</a>';
				}
				
				/* Loop over each instance of the pager */
				for ( i=0, iLen=an.length ; i<iLen ; i++ )
				{
					nNode = an[i];
					if ( !nNode.hasChildNodes() )
					{
						continue;
					}
					
					/* Build up the dynamic list first - html and listeners */
					$('span:eq(0)', nNode)
						.html( sList )
						.children('a').each( fnBind );
					
					/* Update the permanent button's classes */
					anButtons = nNode.getElementsByTagName('a');
					anStatic = [
						anButtons[0], anButtons[1], 
						anButtons[anButtons.length-2], anButtons[anButtons.length-1]
					];
	
					$(anStatic).removeClass( oClasses.sPageButton+" "+oClasses.sPageButtonActive+" "+oClasses.sPageButtonStaticDisabled );
					$([anStatic[0], anStatic[1]]).addClass( 
						(iCurrentPage==1) ?
							oClasses.sPageButtonStaticDisabled :
							oClasses.sPageButton
					);
					$([anStatic[2], anStatic[3]]).addClass(
						(iPages===0 || iCurrentPage===iPages || oSettings._iDisplayLength===-1) ?
							oClasses.sPageButtonStaticDisabled :
							oClasses.sPageButton
					);
				}
			}
		}
	} );
	
	$.extend( DataTable.ext.oSort, {
		/*
		 * text sorting
		 */
		"string-pre": function ( a )
		{
			if ( typeof a != 'string' ) {
				a = (a !== null && a.toString) ? a.toString() : '';
			}
			return a.toLowerCase();
		},
	
		"string-asc": function ( x, y )
		{
			return ((x < y) ? -1 : ((x > y) ? 1 : 0));
		},
		
		"string-desc": function ( x, y )
		{
			return ((x < y) ? 1 : ((x > y) ? -1 : 0));
		},
		
		
		/*
		 * html sorting (ignore html tags)
		 */
		"html-pre": function ( a )
		{
			return a.replace( /<.*?>/g, "" ).toLowerCase();
		},
		
		"html-asc": function ( x, y )
		{
			return ((x < y) ? -1 : ((x > y) ? 1 : 0));
		},
		
		"html-desc": function ( x, y )
		{
			return ((x < y) ? 1 : ((x > y) ? -1 : 0));
		},
		
		
		/*
		 * date sorting
		 */
		"date-pre": function ( a )
		{
			var x = Date.parse( a );
			
			if ( isNaN(x) || x==="" )
			{
				x = Date.parse( "01/01/1970 00:00:00" );
			}
			return x;
		},
	
		"date-asc": function ( x, y )
		{
			return x - y;
		},
		
		"date-desc": function ( x, y )
		{
			return y - x;
		},
		
		
		/*
		 * numerical sorting
		 */
		"numeric-pre": function ( a )
		{
			return (a=="-" || a==="") ? 0 : a*1;
		},
	
		"numeric-asc": function ( x, y )
		{
			return x - y;
		},
		
		"numeric-desc": function ( x, y )
		{
			return y - x;
		}
	} );
	
	
	$.extend( DataTable.ext.aTypes, [
		/*
		 * Function: -
		 * Purpose:  Check to see if a string is numeric
		 * Returns:  string:'numeric' or null
		 * Inputs:   mixed:sText - string to check
		 */
		function ( sData )
		{
			/* Allow zero length strings as a number */
			if ( typeof sData === 'number' )
			{
				return 'numeric';
			}
			else if ( typeof sData !== 'string' )
			{
				return null;
			}
			
			var sValidFirstChars = "0123456789-";
			var sValidChars = "0123456789.";
			var Char;
			var bDecimal = false;
			
			/* Check for a valid first char (no period and allow negatives) */
			Char = sData.charAt(0); 
			if (sValidFirstChars.indexOf(Char) == -1) 
			{
				return null;
			}
			
			/* Check all the other characters are valid */
			for ( var i=1 ; i<sData.length ; i++ ) 
			{
				Char = sData.charAt(i); 
				if (sValidChars.indexOf(Char) == -1) 
				{
					return null;
				}
				
				/* Only allowed one decimal place... */
				if ( Char == "." )
				{
					if ( bDecimal )
					{
						return null;
					}
					bDecimal = true;
				}
			}
			
			return 'numeric';
		},
		
		/*
		 * Function: -
		 * Purpose:  Check to see if a string is actually a formatted date
		 * Returns:  string:'date' or null
		 * Inputs:   string:sText - string to check
		 */
		function ( sData )
		{
			var iParse = Date.parse(sData);
			if ( (iParse !== null && !isNaN(iParse)) || (typeof sData === 'string' && sData.length === 0) )
			{
				return 'date';
			}
			return null;
		},
		
		/*
		 * Function: -
		 * Purpose:  Check to see if a string should be treated as an HTML string
		 * Returns:  string:'html' or null
		 * Inputs:   string:sText - string to check
		 */
		function ( sData )
		{
			if ( typeof sData === 'string' && sData.indexOf('<') != -1 && sData.indexOf('>') != -1 )
			{
				return 'html';
			}
			return null;
		}
	] );
	

	// jQuery aliases
	$.fn.DataTable = DataTable;
	$.fn.dataTable = DataTable;
	$.fn.dataTableSettings = DataTable.settings;
	$.fn.dataTableExt = DataTable.ext;


	// Information about events fired by DataTables - for documentation.
	/**
	 * Draw event, fired whenever the table is redrawn on the page, at the same point as
	 * fnDrawCallback. This may be useful for binding events or performing calculations when
	 * the table is altered at all.
	 *  @name DataTable#draw
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
	 */

	/**
	 * Filter event, fired when the filtering applied to the table (using the build in global
	 * global filter, or column filters) is altered.
	 *  @name DataTable#filter
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
	 */

	/**
	 * Page change event, fired when the paging of the table is altered.
	 *  @name DataTable#page
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
	 */

	/**
	 * Sort event, fired when the sorting applied to the table is altered.
	 *  @name DataTable#sort
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
	 */

	/**
	 * DataTables initialisation complete event, fired when the table is fully drawn,
	 * including Ajax data loaded, if Ajax data is required.
	 *  @name DataTable#init
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} oSettings DataTables settings object
	 *  @param {object} json The JSON object request from the server - only
	 *    present if client-side Ajax sourced data is used</li></ol>
	 */

	/**
	 * State save event, fired when the table has changed state a new state save is required.
	 * This method allows modification of the state saving object prior to actually doing the
	 * save, including addition or other state properties (for plug-ins) or modification
	 * of a DataTables core property.
	 *  @name DataTable#stateSaveParams
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} oSettings DataTables settings object
	 *  @param {object} json The state information to be saved
	 */

	/**
	 * State load event, fired when the table is loading state from the stored data, but
	 * prior to the settings object being modified by the saved state - allowing modification
	 * of the saved state is required or loading of state for a plug-in.
	 *  @name DataTable#stateLoadParams
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} oSettings DataTables settings object
	 *  @param {object} json The saved state information
	 */

	/**
	 * State loaded event, fired when state has been loaded from stored data and the settings
	 * object has been modified by the loaded data.
	 *  @name DataTable#stateLoaded
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} oSettings DataTables settings object
	 *  @param {object} json The saved state information
	 */

	/**
	 * Processing event, fired when DataTables is doing some kind of processing (be it,
	 * sort, filter or anything else). Can be used to indicate to the end user that
	 * there is something happening, or that something has finished.
	 *  @name DataTable#processing
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} oSettings DataTables settings object
	 *  @param {boolean} bShow Flag for if DataTables is doing processing or not
	 */

	/**
	 * Ajax (XHR) event, fired whenever an Ajax request is completed from a request to 
	 * made to the server for new data (note that this trigger is called in fnServerData,
	 * if you override fnServerData and which to use this event, you need to trigger it in
	 * you success function).
	 *  @name DataTable#xhr
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
	 *  @param {object} json JSON returned from the server
	 */

	/**
	 * Destroy event, fired when the DataTable is destroyed by calling fnDestroy or passing
	 * the bDestroy:true parameter in the initialisation object. This can be used to remove
	 * bound events, added DOM nodes, etc.
	 *  @name DataTable#destroy
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
	 */
}));

}(window, document));

console.log('=============== >  jquery.dataTables.js ');

/*
 * File:        FixedHeader.js
 * Version:     2.0.6
 * Description: "Fix" a header at the top of the table, so it scrolls with the table
 * Author:      Allan Jardine (www.sprymedia.co.uk)
 * Created:     Wed 16 Sep 2009 19:46:30 BST
 * Language:    Javascript
 * License:     GPL v2 or BSD 3 point style
 * Project:     Just a little bit of fun - enjoy :-)
 * Contact:     www.sprymedia.co.uk/contact
 * 
 * Copyright 2009-2012 Allan Jardine, all rights reserved.
 *
 * This source file is free software, under either the GPL v2 license or a
 * BSD style license, available at:
 *   http://datatables.net/license_gpl2
 *   http://datatables.net/license_bsd
 */

/*
 * Function: FixedHeader
 * Purpose:  Provide 'fixed' header, footer and columns on an HTML table
 * Returns:  object:FixedHeader - must be called with 'new'
 * Inputs:   mixed:mTable - target table
 *					   1. DataTable object - when using FixedHeader with DataTables, or
 *					   2. HTML table node - when using FixedHeader without DataTables
 *           object:oInit - initialisation settings, with the following properties (each optional)
 *             bool:top -    fix the header (default true)
 *             bool:bottom - fix the footer (default false)
 *             bool:left -   fix the left most column (default false)
 *             bool:right -  fix the right most column (default false)
 *             int:zTop -    fixed header zIndex
 *             int:zBottom - fixed footer zIndex
 *             int:zLeft -   fixed left zIndex
 *             int:zRight -  fixed right zIndex
 */
var FixedHeader = function ( mTable, oInit ) {
	/* Sanity check - you just know it will happen */
	if ( typeof this.fnInit != 'function' )
	{
		alert( "FixedHeader warning: FixedHeader must be initialised with the 'new' keyword." );
		return;
	}
	
	var that = this;
	var oSettings = {
		"aoCache": [],
		"oSides": {
			"top": true,
			"bottom": false,
			"left": false,
			"right": false
		},
		"oZIndexes": {
			"top": 104,
			"bottom": 103,
			"left": 102,
			"right": 101
		},
		"oMes": {
			"iTableWidth": 0,
			"iTableHeight": 0,
			"iTableLeft": 0,
			"iTableRight": 0, /* note this is left+width, not actually "right" */
			"iTableTop": 0,
			"iTableBottom": 0 /* note this is top+height, not actually "bottom" */
		},
		"oOffset": {
			"top": 0
		},
		"nTable": null,
		"bUseAbsPos": false,
		"bFooter": false
	};
	
	/*
	 * Function: fnGetSettings
	 * Purpose:  Get the settings for this object
	 * Returns:  object: - settings object
	 * Inputs:   -
	 */
	this.fnGetSettings = function () {
		return oSettings;
	};
	
	/*
	 * Function: fnUpdate
	 * Purpose:  Update the positioning and copies of the fixed elements
	 * Returns:  -
	 * Inputs:   -
	 */
	this.fnUpdate = function () {
		this._fnUpdateClones();
		this._fnUpdatePositions();
	};
	
	/*
	 * Function: fnPosition
	 * Purpose:  Update the positioning of the fixed elements
	 * Returns:  -
	 * Inputs:   -
	 */
	this.fnPosition = function () {
		this._fnUpdatePositions();
	};
	
	/* Let's do it */
	this.fnInit( mTable, oInit );
	
	/* Store the instance on the DataTables object for easy access */
	if ( typeof mTable.fnSettings == 'function' )
	{
		mTable._oPluginFixedHeader = this;
	}
};


/*
 * Variable: FixedHeader
 * Purpose:  Prototype for FixedHeader
 * Scope:    global
 */
FixedHeader.prototype = {
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Initialisation
	 */
	
	/*
	 * Function: fnInit
	 * Purpose:  The "constructor"
	 * Returns:  -
	 * Inputs:   {as FixedHeader function}
	 */
	fnInit: function ( oTable, oInit )
	{
		var s = this.fnGetSettings();
		var that = this;
		
		/* Record the user definable settings */
		this.fnInitSettings( s, oInit );
		
		/* DataTables specific stuff */
		if ( typeof oTable.fnSettings == 'function' )
		{
			if ( typeof oTable.fnVersionCheck == 'functon' &&
			     oTable.fnVersionCheck( '1.6.0' ) !== true )
			{
				alert( "FixedHeader 2 required DataTables 1.6.0 or later. "+
					"Please upgrade your DataTables installation" );
				return;
			}
			
			var oDtSettings = oTable.fnSettings();
			
			if ( oDtSettings.oScroll.sX != "" || oDtSettings.oScroll.sY != "" )
			{
				alert( "FixedHeader 2 is not supported with DataTables' scrolling mode at this time" );
				return;
			}
			
			s.nTable = oDtSettings.nTable;
			oDtSettings.aoDrawCallback.push( {
				"fn": function () {
					FixedHeader.fnMeasure();
					that._fnUpdateClones.call(that);
					that._fnUpdatePositions.call(that);
				},
				"sName": "FixedHeader"
			} );
		}
		else
		{
			s.nTable = oTable;
		}
		
		s.bFooter = ($('>tfoot', s.nTable).length > 0) ? true : false;
		
		/* "Detect" browsers that don't support absolute positioing - or have bugs */
		s.bUseAbsPos = (jQuery.browser.msie && (jQuery.browser.version=="6.0"||jQuery.browser.version=="7.0"));
		
		/* Add the 'sides' that are fixed */
		if ( s.oSides.top )
		{
			s.aoCache.push( that._fnCloneTable( "fixedHeader", "FixedHeader_Header", that._fnCloneThead ) );
		}
		if ( s.oSides.bottom )
		{
			s.aoCache.push( that._fnCloneTable( "fixedFooter", "FixedHeader_Footer", that._fnCloneTfoot ) );
		}
		if ( s.oSides.left )
		{
			s.aoCache.push( that._fnCloneTable( "fixedLeft", "FixedHeader_Left", that._fnCloneTLeft ) );
		}
		if ( s.oSides.right )
		{
			s.aoCache.push( that._fnCloneTable( "fixedRight", "FixedHeader_Right", that._fnCloneTRight ) );
		}
		
		/* Event listeners for window movement */
		FixedHeader.afnScroll.push( function () {
			that._fnUpdatePositions.call(that);
		} );
		
		jQuery(window).resize( function () {
			FixedHeader.fnMeasure();
			that._fnUpdateClones.call(that);
			that._fnUpdatePositions.call(that);
		} );
		
		/* Get things right to start with */
		FixedHeader.fnMeasure();
		that._fnUpdateClones();
		that._fnUpdatePositions();
	},
	
	
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Support functions
	 */
	
	/*
	 * Function: fnInitSettings
	 * Purpose:  Take the user's settings and copy them to our local store
	 * Returns:  -
	 * Inputs:   object:s - the local settings object
	 *           object:oInit - the user's settings object
	 */
	fnInitSettings: function ( s, oInit )
	{
		if ( typeof oInit != 'undefined' )
		{
			if ( typeof oInit.top != 'undefined' ) {
				s.oSides.top = oInit.top;
			}
			if ( typeof oInit.bottom != 'undefined' ) {
				s.oSides.bottom = oInit.bottom;
			}
			if ( typeof oInit.left != 'undefined' ) {
				s.oSides.left = oInit.left;
			}
			if ( typeof oInit.right != 'undefined' ) {
				s.oSides.right = oInit.right;
			}
			
			if ( typeof oInit.zTop != 'undefined' ) {
				s.oZIndexes.top = oInit.zTop;
			}
			if ( typeof oInit.zBottom != 'undefined' ) {
				s.oZIndexes.bottom = oInit.zBottom;
			}
			if ( typeof oInit.zLeft != 'undefined' ) {
				s.oZIndexes.left = oInit.zLeft;
			}
			if ( typeof oInit.zRight != 'undefined' ) {
				s.oZIndexes.right = oInit.zRight;
			}

			if ( typeof oInit.offsetTop != 'undefined' ) {
				s.oOffset.top = oInit.offsetTop;
			}
		}
		
		/* Detect browsers which have poor position:fixed support so we can use absolute positions.
		 * This is much slower since the position must be updated for each scroll, but widens
		 * compatibility
		 */
		s.bUseAbsPos = (jQuery.browser.msie && 
			(jQuery.browser.version=="6.0"||jQuery.browser.version=="7.0"));
	},
	
	/*
	 * Function: _fnCloneTable
	 * Purpose:  Clone the table node and do basic initialisation
	 * Returns:  -
	 * Inputs:   -
	 */
	_fnCloneTable: function ( sType, sClass, fnClone )
	{
		var s = this.fnGetSettings();
		var nCTable;
		
		/* We know that the table _MUST_ has a DIV wrapped around it, because this is simply how
		 * DataTables works. Therefore, we can set this to be relatively position (if it is not
		 * alreadu absolute, and use this as the base point for the cloned header
		 */
		if ( jQuery(s.nTable.parentNode).css('position') != "absolute" )
		{
			s.nTable.parentNode.style.position = "relative";
		}
		
		/* Just a shallow clone will do - we only want the table node */
		nCTable = s.nTable.cloneNode( false );
		nCTable.removeAttribute( 'id' );
		
		var nDiv = document.createElement( 'div' );
		nDiv.style.position = "absolute";
		nDiv.style.top = "0px";
		nDiv.style.left = "0px";
		nDiv.className += " FixedHeader_Cloned "+sType+" "+sClass;
		
		/* Set the zIndexes */
		if ( sType == "fixedHeader" )
		{
			nDiv.style.zIndex = s.oZIndexes.top;
		}
		if ( sType == "fixedFooter" )
		{
			nDiv.style.zIndex = s.oZIndexes.bottom;
		}
		if ( sType == "fixedLeft" )
		{
			nDiv.style.zIndex = s.oZIndexes.left;
		}
		else if ( sType == "fixedRight" )
		{
			nDiv.style.zIndex = s.oZIndexes.right;
		}

		/* remove margins since we are going to poistion it absolutely */
		nCTable.style.margin = "0";
		
		/* Insert the newly cloned table into the DOM, on top of the "real" header */
		nDiv.appendChild( nCTable );
		document.body.appendChild( nDiv );
		
		return {
			"nNode": nCTable,
			"nWrapper": nDiv,
			"sType": sType,
			"sPosition": "",
			"sTop": "",
			"sLeft": "",
			"fnClone": fnClone
		};
	},
	
	/*
	 * Function: _fnUpdatePositions
	 * Purpose:  Get the current positioning of the table in the DOM
	 * Returns:  -
	 * Inputs:   -
	 */
	_fnMeasure: function ()
	{
		var
			s = this.fnGetSettings(),
			m = s.oMes,
			jqTable = jQuery(s.nTable),
			oOffset = jqTable.offset(),
			iParentScrollTop = this._fnSumScroll( s.nTable.parentNode, 'scrollTop' ),
			iParentScrollLeft = this._fnSumScroll( s.nTable.parentNode, 'scrollLeft' );
		
		m.iTableWidth = jqTable.outerWidth();
		m.iTableHeight = jqTable.outerHeight();
		m.iTableLeft = oOffset.left + s.nTable.parentNode.scrollLeft;
		m.iTableTop = oOffset.top + iParentScrollTop;
		m.iTableRight = m.iTableLeft + m.iTableWidth;
		m.iTableRight = FixedHeader.oDoc.iWidth - m.iTableLeft - m.iTableWidth;
		m.iTableBottom = FixedHeader.oDoc.iHeight - m.iTableTop - m.iTableHeight;
	},
	
	/*
	 * Function: _fnSumScroll
	 * Purpose:  Sum node parameters all the way to the top
	 * Returns:  int: sum
	 * Inputs:   node:n - node to consider
	 *           string:side - scrollTop or scrollLeft
	 */
	_fnSumScroll: function ( n, side )
	{
		var i = n[side];
		while ( n = n.parentNode )
		{
			if ( n.nodeName == 'HTML' || n.nodeName == 'BODY' )
			{
				break;
			}
			i = n[side];
		}
		return i;
	},
	
	/*
	 * Function: _fnUpdatePositions
	 * Purpose:  Loop over the fixed elements for this table and update their positions
	 * Returns:  -
	 * Inputs:   -
	 */
	_fnUpdatePositions: function ()
	{
		var s = this.fnGetSettings();
		this._fnMeasure();
		
		for ( var i=0, iLen=s.aoCache.length ; i<iLen ; i++ )
		{
			if ( s.aoCache[i].sType == "fixedHeader" )
			{
				this._fnScrollFixedHeader( s.aoCache[i] );
			}
			else if ( s.aoCache[i].sType == "fixedFooter" )
			{
				this._fnScrollFixedFooter( s.aoCache[i] );
			}
			else if ( s.aoCache[i].sType == "fixedLeft" )
			{
				this._fnScrollHorizontalLeft( s.aoCache[i] );
			}
			else
			{
				this._fnScrollHorizontalRight( s.aoCache[i] );
			}
		}
	},
	
	/*
	 * Function: _fnUpdateClones
	 * Purpose:  Loop over the fixed elements for this table and call their cloning functions
	 * Returns:  -
	 * Inputs:   -
	 */
	_fnUpdateClones: function ()
	{
		var s = this.fnGetSettings();
		for ( var i=0, iLen=s.aoCache.length ; i<iLen ; i++ )
		{
			s.aoCache[i].fnClone.call( this, s.aoCache[i] );
		}
	},
	
	
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Scrolling functions
	 */
	
	/*
	 * Function: _fnScrollHorizontalLeft
	 * Purpose:  Update the positioning of the scrolling elements
	 * Returns:  -
	 * Inputs:   object:oCache - the cahced values for this fixed element
	 */
	_fnScrollHorizontalRight: function ( oCache )
	{
		var
			s = this.fnGetSettings(),
			oMes = s.oMes,
			oWin = FixedHeader.oWin,
			oDoc = FixedHeader.oDoc,
			nTable = oCache.nWrapper,
			iFixedWidth = jQuery(nTable).outerWidth();
		
		if ( oWin.iScrollRight < oMes.iTableRight )
		{
			/* Fully right aligned */
			this._fnUpdateCache( oCache, 'sPosition', 'absolute', 'position', nTable.style );
			this._fnUpdateCache( oCache, 'sTop', oMes.iTableTop+"px", 'top', nTable.style );
			this._fnUpdateCache( oCache, 'sLeft', (oMes.iTableLeft+oMes.iTableWidth-iFixedWidth)+"px", 'left', nTable.style );	
		}
		else if ( oMes.iTableLeft < oDoc.iWidth-oWin.iScrollRight-iFixedWidth )
		{
			/* Middle */
			if ( s.bUseAbsPos )
			{
				this._fnUpdateCache( oCache, 'sPosition', 'absolute', 'position', nTable.style );
				this._fnUpdateCache( oCache, 'sTop', oMes.iTableTop+"px", 'top', nTable.style );
				this._fnUpdateCache( oCache, 'sLeft', (oDoc.iWidth-oWin.iScrollRight-iFixedWidth)+"px", 'left', nTable.style );
			}
			else
			{
				this._fnUpdateCache( oCache, 'sPosition', 'fixed', 'position', nTable.style );
				this._fnUpdateCache( oCache, 'sTop', (oMes.iTableTop-oWin.iScrollTop)+"px", 'top', nTable.style );
				this._fnUpdateCache( oCache, 'sLeft', (oWin.iWidth-iFixedWidth)+"px", 'left', nTable.style );
			}	
		}
		else
		{
			/* Fully left aligned */
			this._fnUpdateCache( oCache, 'sPosition', 'absolute', 'position', nTable.style );
			this._fnUpdateCache( oCache, 'sTop', oMes.iTableTop+"px", 'top', nTable.style );
			this._fnUpdateCache( oCache, 'sLeft', oMes.iTableLeft+"px", 'left', nTable.style );	
		}
	},
	
	/*
	 * Function: _fnScrollHorizontalLeft
	 * Purpose:  Update the positioning of the scrolling elements
	 * Returns:  -
	 * Inputs:   object:oCache - the cahced values for this fixed element
	 */
	_fnScrollHorizontalLeft: function ( oCache )
	{
		var
			s = this.fnGetSettings(),
			oMes = s.oMes,
			oWin = FixedHeader.oWin,
			oDoc = FixedHeader.oDoc,
			nTable = oCache.nWrapper,
			iCellWidth = jQuery(nTable).outerWidth();
		
		if ( oWin.iScrollLeft < oMes.iTableLeft )
		{
			/* Fully left align */
			this._fnUpdateCache( oCache, 'sPosition', 'absolute', 'position', nTable.style );
			this._fnUpdateCache( oCache, 'sTop', oMes.iTableTop+"px", 'top', nTable.style );
			this._fnUpdateCache( oCache, 'sLeft', oMes.iTableLeft+"px", 'left', nTable.style );	
		}
		else if ( oWin.iScrollLeft < oMes.iTableLeft+oMes.iTableWidth-iCellWidth )
		{
			/* Middle */
			if ( s.bUseAbsPos )
			{
				this._fnUpdateCache( oCache, 'sPosition', 'absolute', 'position', nTable.style );
				this._fnUpdateCache( oCache, 'sTop', oMes.iTableTop+"px", 'top', nTable.style );
				this._fnUpdateCache( oCache, 'sLeft', oWin.iScrollLeft+"px", 'left', nTable.style );
			}
			else
			{
				this._fnUpdateCache( oCache, 'sPosition', 'fixed', 'position', nTable.style );
				this._fnUpdateCache( oCache, 'sTop', (oMes.iTableTop-oWin.iScrollTop)+"px", 'top', nTable.style );
				this._fnUpdateCache( oCache, 'sLeft', "0px", 'left', nTable.style );
			}	
		}
		else
		{
			/* Fully right align */
			this._fnUpdateCache( oCache, 'sPosition', 'absolute', 'position', nTable.style );
			this._fnUpdateCache( oCache, 'sTop', oMes.iTableTop+"px", 'top', nTable.style );
			this._fnUpdateCache( oCache, 'sLeft', (oMes.iTableLeft+oMes.iTableWidth-iCellWidth)+"px", 'left', nTable.style );	
		}
	},
	
	/*
	 * Function: _fnScrollFixedFooter
	 * Purpose:  Update the positioning of the scrolling elements
	 * Returns:  -
	 * Inputs:   object:oCache - the cahced values for this fixed element
	 */
	_fnScrollFixedFooter: function ( oCache )
	{
		var
			s = this.fnGetSettings(),
			oMes = s.oMes,
			oWin = FixedHeader.oWin,
			oDoc = FixedHeader.oDoc,
			nTable = oCache.nWrapper,
			iTheadHeight = jQuery("thead", s.nTable).outerHeight(),
			iCellHeight = jQuery(nTable).outerHeight();
		
		if ( oWin.iScrollBottom < oMes.iTableBottom )
		{
			/* Below */
			this._fnUpdateCache( oCache, 'sPosition', 'absolute', 'position', nTable.style );
			this._fnUpdateCache( oCache, 'sTop', (oMes.iTableTop+oMes.iTableHeight-iCellHeight)+"px", 'top', nTable.style );
			this._fnUpdateCache( oCache, 'sLeft', oMes.iTableLeft+"px", 'left', nTable.style );	
		}
		else if ( oWin.iScrollBottom < oMes.iTableBottom+oMes.iTableHeight-iCellHeight-iTheadHeight )
		{
			/* Middle */
			if ( s.bUseAbsPos )
			{
				this._fnUpdateCache( oCache, 'sPosition', "absolute", 'position', nTable.style );
				this._fnUpdateCache( oCache, 'sTop', (oDoc.iHeight-oWin.iScrollBottom-iCellHeight)+"px", 'top', nTable.style );
				this._fnUpdateCache( oCache, 'sLeft', oMes.iTableLeft+"px", 'left', nTable.style );
			}
			else
			{
				this._fnUpdateCache( oCache, 'sPosition', 'fixed', 'position', nTable.style );
				this._fnUpdateCache( oCache, 'sTop', (oWin.iHeight-iCellHeight)+"px", 'top', nTable.style );
				this._fnUpdateCache( oCache, 'sLeft', (oMes.iTableLeft-oWin.iScrollLeft)+"px", 'left', nTable.style );	
			}
		}
		else
		{
			/* Above */
			this._fnUpdateCache( oCache, 'sPosition', 'absolute', 'position', nTable.style );
			this._fnUpdateCache( oCache, 'sTop', (oMes.iTableTop+iCellHeight)+"px", 'top', nTable.style );
			this._fnUpdateCache( oCache, 'sLeft', oMes.iTableLeft+"px", 'left', nTable.style );	
		}
	},
	
	/*
	 * Function: _fnScrollFixedHeader
	 * Purpose:  Update the positioning of the scrolling elements
	 * Returns:  -
	 * Inputs:   object:oCache - the cahced values for this fixed element
	 */
	_fnScrollFixedHeader: function ( oCache )
	{
		var
			s = this.fnGetSettings(),
			oMes = s.oMes,
			oWin = FixedHeader.oWin,
			oDoc = FixedHeader.oDoc,
			nTable = oCache.nWrapper,
			iTbodyHeight = 0,
			anTbodies = s.nTable.getElementsByTagName('tbody');

		for (var i = 0; i < anTbodies.length; ++i) {
			iTbodyHeight += anTbodies[i].offsetHeight;
		}

		if ( oMes.iTableTop > oWin.iScrollTop + s.oOffset.top )
		{
			/* Above the table */
			this._fnUpdateCache( oCache, 'sPosition', "absolute", 'position', nTable.style );
			this._fnUpdateCache( oCache, 'sTop', oMes.iTableTop+"px", 'top', nTable.style );
			this._fnUpdateCache( oCache, 'sLeft', oMes.iTableLeft+"px", 'left', nTable.style );
		}
		else if ( oWin.iScrollTop + s.oOffset.top > oMes.iTableTop+iTbodyHeight )
		{
			/* At the bottom of the table */
			this._fnUpdateCache( oCache, 'sPosition', "absolute", 'position', nTable.style );
			this._fnUpdateCache( oCache, 'sTop', (oMes.iTableTop+iTbodyHeight)+"px", 'top', nTable.style );
			this._fnUpdateCache( oCache, 'sLeft', oMes.iTableLeft+"px", 'left', nTable.style );
		}
		else
		{
			/* In the middle of the table */
			if ( s.bUseAbsPos )
			{
				this._fnUpdateCache( oCache, 'sPosition', "absolute", 'position', nTable.style );
				this._fnUpdateCache( oCache, 'sTop', oWin.iScrollTop+"px", 'top', nTable.style );
				this._fnUpdateCache( oCache, 'sLeft', oMes.iTableLeft+"px", 'left', nTable.style );
			}
			else
			{
				this._fnUpdateCache( oCache, 'sPosition', 'fixed', 'position', nTable.style );
				this._fnUpdateCache( oCache, 'sTop', s.oOffset.top+"px", 'top', nTable.style );
				this._fnUpdateCache( oCache, 'sLeft', (oMes.iTableLeft-oWin.iScrollLeft)+"px", 'left', nTable.style );
			}
		}
	},
	
	/*
	 * Function: _fnUpdateCache
	 * Purpose:  Check the cache and update cache and value if needed
	 * Returns:  -
	 * Inputs:   object:oCache - local cache object
	 *           string:sCache - cache property
	 *           string:sSet - value to set
	 *           string:sProperty - object property to set
	 *           object:oObj - object to update
	 */
	_fnUpdateCache: function ( oCache, sCache, sSet, sProperty, oObj )
	{
		if ( oCache[sCache] != sSet )
		{
			oObj[sProperty] = sSet;
			oCache[sCache] = sSet;
		}
	},
	
	
	
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Cloning functions
	 */
	
	/*
	 * Function: _fnCloneThead
	 * Purpose:  Clone the thead element
	 * Returns:  -
	 * Inputs:   object:oCache - the cahced values for this fixed element
	 */
	_fnCloneThead: function ( oCache )
	{
		var s = this.fnGetSettings();
		var nTable = oCache.nNode;
		
		/* Set the wrapper width to match that of the cloned table */
		oCache.nWrapper.style.width = jQuery(s.nTable).outerWidth()+"px";
		
		/* Remove any children the cloned table has */
		while ( nTable.childNodes.length > 0 )
		{
			jQuery('thead th', nTable).unbind( 'click' );
			nTable.removeChild( nTable.childNodes[0] );
		}
		
		/* Clone the DataTables header */
		var nThead = jQuery('thead', s.nTable).clone(true)[0];
		nTable.appendChild( nThead );
		
		/* Copy the widths across - apparently a clone isn't good enough for this */
		jQuery("thead>tr th", s.nTable).each( function (i) {
			jQuery("thead>tr th:eq("+i+")", nTable).width( jQuery(this).width() );
		} );
		
		jQuery("thead>tr td", s.nTable).each( function (i) {
			jQuery("thead>tr td:eq("+i+")", nTable).width( jQuery(this).width() );
		} );
	},
	
	/*
	 * Function: _fnCloneTfoot
	 * Purpose:  Clone the tfoot element
	 * Returns:  -
	 * Inputs:   object:oCache - the cahced values for this fixed element
	 */
	_fnCloneTfoot: function ( oCache )
	{
		var s = this.fnGetSettings();
		var nTable = oCache.nNode;
		
		/* Set the wrapper width to match that of the cloned table */
		oCache.nWrapper.style.width = jQuery(s.nTable).outerWidth()+"px";
		
		/* Remove any children the cloned table has */
		while ( nTable.childNodes.length > 0 )
		{
			nTable.removeChild( nTable.childNodes[0] );
		}
		
		/* Clone the DataTables footer */
		var nTfoot = jQuery('tfoot', s.nTable).clone(true)[0];
		nTable.appendChild( nTfoot );
		
		/* Copy the widths across - apparently a clone isn't good enough for this */
		jQuery("tfoot:eq(0)>tr th", s.nTable).each( function (i) {
			jQuery("tfoot:eq(0)>tr th:eq("+i+")", nTable).width( jQuery(this).width() );
		} );
		
		jQuery("tfoot:eq(0)>tr td", s.nTable).each( function (i) {
			jQuery("tfoot:eq(0)>tr th:eq("+i+")", nTable)[0].style.width( jQuery(this).width() );
		} );
	},
	
	/*
	 * Function: _fnCloneTLeft
	 * Purpose:  Clone the left column
	 * Returns:  -
	 * Inputs:   object:oCache - the cached values for this fixed element
	 */
	_fnCloneTLeft: function ( oCache )
	{
		var s = this.fnGetSettings();
		var nTable = oCache.nNode;
		var nBody = $('tbody', s.nTable)[0];
		var iCols = $('tbody tr:eq(0) td', s.nTable).length;
		var bRubbishOldIE = ($.browser.msie && ($.browser.version == "6.0" || $.browser.version == "7.0"));
		
		/* Remove any children the cloned table has */
		while ( nTable.childNodes.length > 0 )
		{
			nTable.removeChild( nTable.childNodes[0] );
		}
		
		/* Is this the most efficient way to do this - it looks horrible... */
		nTable.appendChild( jQuery("thead", s.nTable).clone(true)[0] );
		nTable.appendChild( jQuery("tbody", s.nTable).clone(true)[0] );
		if ( s.bFooter )
		{
			nTable.appendChild( jQuery("tfoot", s.nTable).clone(true)[0] );
		}
		
		/* Remove unneeded cells */
		$('thead tr', nTable).each( function (k) {
			$('th:gt(0)', this).remove();
		} );

		$('tfoot tr', nTable).each( function (k) {
			$('th:gt(0)', this).remove();
		} );

		$('tbody tr', nTable).each( function (k) {
			$('td:gt(0)', this).remove();
		} );
		
		this.fnEqualiseHeights( 'tbody', nBody.parentNode, nTable );
		
		var iWidth = jQuery('thead tr th:eq(0)', s.nTable).outerWidth();
		nTable.style.width = iWidth+"px";
		oCache.nWrapper.style.width = iWidth+"px";
	},
	
	/*
	 * Function: _fnCloneTRight
	 * Purpose:  Clone the right most colun
	 * Returns:  -
	 * Inputs:   object:oCache - the cahced values for this fixed element
	 */
	_fnCloneTRight: function ( oCache )
	{
		var s = this.fnGetSettings();
		var nBody = $('tbody', s.nTable)[0];
		var nTable = oCache.nNode;
		var iCols = jQuery('tbody tr:eq(0) td', s.nTable).length;
		var bRubbishOldIE = ($.browser.msie && ($.browser.version == "6.0" || $.browser.version == "7.0"));
		
		/* Remove any children the cloned table has */
		while ( nTable.childNodes.length > 0 )
		{
			nTable.removeChild( nTable.childNodes[0] );
		}
		
		/* Is this the most efficient way to do this - it looks horrible... */
		nTable.appendChild( jQuery("thead", s.nTable).clone(true)[0] );
		nTable.appendChild( jQuery("tbody", s.nTable).clone(true)[0] );
		if ( s.bFooter )
		{
			nTable.appendChild( jQuery("tfoot", s.nTable).clone(true)[0] );
		}
		jQuery('thead tr th:not(:nth-child('+iCols+'n))', nTable).remove();
		jQuery('tfoot tr th:not(:nth-child('+iCols+'n))', nTable).remove();
		
		/* Remove unneeded cells */
		$('tbody tr', nTable).each( function (k) {
			$('td:lt('+(iCols-1)+')', this).remove();
		} );
		
		this.fnEqualiseHeights( 'tbody', nBody.parentNode, nTable );
		
		var iWidth = jQuery('thead tr th:eq('+(iCols-1)+')', s.nTable).outerWidth();
		nTable.style.width = iWidth+"px";
		oCache.nWrapper.style.width = iWidth+"px";
	},
	
	
	/**
	 * Equalise the heights of the rows in a given table node in a cross browser way. Note that this
	 * is more or less lifted as is from FixedColumns
	 *  @method  fnEqualiseHeights
	 *  @returns void
	 *  @param   {string} parent Node type - thead, tbody or tfoot
	 *  @param   {element} original Original node to take the heights from
	 *  @param   {element} clone Copy the heights to
	 *  @private
	 */
	"fnEqualiseHeights": function ( parent, original, clone )
	{
		var that = this,
			jqBoxHack = $(parent+' tr:eq(0)', original).children(':eq(0)'),
			iBoxHack = jqBoxHack.outerHeight() - jqBoxHack.height(),
			bRubbishOldIE = ($.browser.msie && ($.browser.version == "6.0" || $.browser.version == "7.0"));
		
		/* Remove cells which are not needed and copy the height from the original table */
		$(parent+' tr', clone).each( function (k) {
			/* Can we use some kind of object detection here?! This is very nasty - damn browsers */
			if ( $.browser.mozilla || $.browser.opera )
			{
				$(this).children().height( $(parent+' tr:eq('+k+')', original).outerHeight() );
			}
			else
			{
				$(this).children().height( $(parent+' tr:eq('+k+')', original).outerHeight() - iBoxHack );
			}
			
			if ( !bRubbishOldIE )
			{
				$(parent+' tr:eq('+k+')', original).height( $(parent+' tr:eq('+k+')', original).outerHeight() );		
			}
		} );
	}
};

	
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Static properties and methods
 *   We use these for speed! This information is common to all instances of FixedHeader, so no
 * point if having them calculated and stored for each different instance.
 */

/*
 * Variable: oWin
 * Purpose:  Store information about the window positioning
 * Scope:    FixedHeader
 */
FixedHeader.oWin = {
	"iScrollTop": 0,
	"iScrollRight": 0,
	"iScrollBottom": 0,
	"iScrollLeft": 0,
	"iHeight": 0,
	"iWidth": 0
};

/*
 * Variable: oDoc
 * Purpose:  Store information about the document size
 * Scope:    FixedHeader
 */
FixedHeader.oDoc = {
	"iHeight": 0,
	"iWidth": 0
};

/*
 * Variable: afnScroll
 * Purpose:  Array of functions that are to be used for the scrolling components
 * Scope:    FixedHeader
 */
FixedHeader.afnScroll = [];

/*
 * Function: fnMeasure
 * Purpose:  Update the measurements for the window and document
 * Returns:  -
 * Inputs:   -
 */
FixedHeader.fnMeasure = function ()
{
	var
		jqWin = jQuery(window),
		jqDoc = jQuery(document),
		oWin = FixedHeader.oWin,
		oDoc = FixedHeader.oDoc;
	
	oDoc.iHeight = jqDoc.height();
	oDoc.iWidth = jqDoc.width();
	
	oWin.iHeight = jqWin.height();
	oWin.iWidth = jqWin.width();
	oWin.iScrollTop = jqWin.scrollTop();
	oWin.iScrollLeft = jqWin.scrollLeft();
	oWin.iScrollRight = oDoc.iWidth - oWin.iScrollLeft - oWin.iWidth;
	oWin.iScrollBottom = oDoc.iHeight - oWin.iScrollTop - oWin.iHeight;
};


FixedHeader.VERSION = "2.0.6";
FixedHeader.prototype.VERSION = FixedHeader.VERSION;

	
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Global processing
 */

/*
 * Just one 'scroll' event handler in FixedHeader, which calls the required components. This is
 * done as an optimisation, to reduce calculation and proagation time
 */
jQuery(window).scroll( function () {
	FixedHeader.fnMeasure();
	for ( var i=0, iLen=FixedHeader.afnScroll.length ; i<iLen ; i++ )
	{
		FixedHeader.afnScroll[i]();
	}
} );
console.log('=============== >  FixedHeader.js ');

//Create a jquery plugin that prints the given element.
// courtesy of http://www.bennadel.com/blog/1591-Ask-Ben-Print-Part-Of-A-Web-Page-With-jQuery.htm
jQuery.fn.print = function(){
//	NOTE: We are trimming the jQuery collection down to the
//	first element in the collection.
	if (this.size() > 1){
		this.eq( 0 ).print();
		return;
	} else if (!this.size()){
		return;
	}

//	ASSERT: At this point, we know that the current jQuery
//	collection (as defined by THIS), contains only one
//	printable element.

//	Create a random name for the print frame.
	var strFrameName = ("printer-" + (new Date()).getTime());

//	Create an iFrame with the new name.
	var jFrame = $( "<iframe id='" + strFrameName + "' name='" + strFrameName + "'>" );

//	Hide the frame (sort of) and attach to the body.
	jFrame
	.css( "width", "1px" )
	.css( "height", "1px" )
	.css( "position", "absolute" )
	.css( "left", "-9999px" )
	.appendTo( $( "body:first" ) )
	;

//	Get a FRAMES reference to the new frame.
	var objFrame = window.frames[ strFrameName ];

//	Get a reference to the DOM in the new frame.
	var objDoc = objFrame.document;

//	Grab all the style tags and copy to the new
//	document so that we capture look and feel of
//	the current document.

//	Create a temp document DIV to hold the style tags.
//	This is the only way I could find to get the style
//	tags into IE.
	var jStyleDiv = $( "<div>" ).append(
			$( "style" ).clone()
	);

//	Write the HTML for the document. In this, we will
//	write out the HTML of the current element.
	objDoc.open();
	objDoc.write( "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">" );
	objDoc.write( "<html>" );
	objDoc.write( "<body>" );
	objDoc.write( "<head>" );
	objDoc.write( "<title>" );
	objDoc.write( document.title );
	objDoc.write( "</title>" );
	objDoc.write( jStyleDiv.html());
	objDoc.write( "</head>" );
	objDoc.write( this.html() );
	objDoc.write( "</body>" );
	objDoc.write( "</html>" );
	objDoc.close();
//	Print the document.
	objFrame.focus();
	objFrame.print();

//	Have the frame remove itself in about a minute so that
//	we don't build up too many of these frames.
	setTimeout(
			function(){
				jFrame.remove();
			},
			(60 * 1000)
	);
}
console.log('=============== >  jquery.prints.js ');

/*
 * jQuery Tooltip plugin 1.3
 *
 * http://bassistance.de/jquery-plugins/jquery-plugin-tooltip/
 * http://docs.jquery.com/Plugins/Tooltip
 *
 * Copyright (c) 2006 - 2008 JÃ¶rn Zaefferer
 *
 * $Id: jquery.tooltip.js 5741 2008-06-21 15:22:16Z joern.zaefferer $
 * 
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 */
 
;(function($) {
	
		// the tooltip element
	var helper = {},
		// the current tooltipped element
		current,
		// the title of the current element, used for restoring
		title,
		// timeout id for delayed tooltips
		tID,
		// IE 5.5 or 6
		IE = $.browser.msie && /MSIE\s(5\.5|6\.)/.test(navigator.userAgent),
		// flag for mouse tracking
		track = false;
	
	$.tooltip = {
		blocked: false,
		defaults: {
			delay: 200,
			fade: false,
			showURL: true,
			extraClass: "",
			top: 15,
			left: 15,
			id: "tooltip"
		},
		block: function() {
			$.tooltip.blocked = !$.tooltip.blocked;
		}
	};
	
	$.fn.extend({
		tooltip: function(settings) {
			settings = $.extend({}, $.tooltip.defaults, settings);
			createHelper(settings);
			return this.each(function() {
					$.data(this, "tooltip", settings);
					this.tOpacity = helper.parent.css("opacity");
					// copy tooltip into its own expando and remove the title
					this.tooltipText = this.title;
					$(this).removeAttr("title");
					// also remove alt attribute to prevent default tooltip in IE
					this.alt = "";
				})
				.mouseover(save)
				.mouseout(hide)
				.click(hide);
		},
		fixPNG: IE ? function() {
			return this.each(function () {
				var image = $(this).css('backgroundImage');
				if (image.match(/^url\(["']?(.*\.png)["']?\)$/i)) {
					image = RegExp.$1;
					$(this).css({
						'backgroundImage': 'none',
						'filter': "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='" + image + "')"
					}).each(function () {
						var position = $(this).css('position');
						if (position != 'absolute' && position != 'relative')
							$(this).css('position', 'relative');
					});
				}
			});
		} : function() { return this; },
		unfixPNG: IE ? function() {
			return this.each(function () {
				$(this).css({'filter': '', backgroundImage: ''});
			});
		} : function() { return this; },
		hideWhenEmpty: function() {
			return this.each(function() {
				$(this)[ $(this).html() ? "show" : "hide" ]();
			});
		},
		url: function() {
			return this.attr('href') || this.attr('src');
		}
	});
	
	function createHelper(settings) {
		// there can be only one tooltip helper
		if( helper.parent )
			return;
		// create the helper, h3 for title, div for url
		helper.parent = $('<div id="' + settings.id + '"><h3></h3><div class="body"></div><div class="url"></div></div>')
			// add to document
			.appendTo(document.body)
			// hide it at first
			.hide();
			
		// apply bgiframe if available
		if ( $.fn.bgiframe )
			helper.parent.bgiframe();
		
		// save references to title and url elements
		helper.title = $('h3', helper.parent);
		helper.body = $('div.body', helper.parent);
		helper.url = $('div.url', helper.parent);
	}
	
	function settings(element) {
		return $.data(element, "tooltip");
	}
	
	// main event handler to start showing tooltips
	function handle(event) {
		// show helper, either with timeout or on instant
		if( settings(this).delay )
			tID = setTimeout(show, settings(this).delay);
		else
			show();
		
		// if selected, update the helper position when the mouse moves
		track = !!settings(this).track;
		$(document.body).bind('mousemove', update);
			
		// update at least once
		update(event);
	}
	
	// save elements title before the tooltip is displayed
	function save() {
		// if this is the current source, or it has no title (occurs with click event), stop
		if ( $.tooltip.blocked || this == current || (!this.tooltipText && !settings(this).bodyHandler) )
			return;

		// save current
		current = this;
		title = this.tooltipText;
		
		if ( settings(this).bodyHandler ) {
			helper.title.hide();
			var bodyContent = settings(this).bodyHandler.call(this);
			if (bodyContent.nodeType || bodyContent.jquery) {
				helper.body.empty().append(bodyContent)
			} else {
				helper.body.html( bodyContent );
			}
			helper.body.show();
		} else if ( settings(this).showBody ) {
			var parts = title.split(settings(this).showBody);
			helper.title.html(parts.shift()).show();
			helper.body.empty();
			for(var i = 0, part; (part = parts[i]); i++) {
				if(i > 0)
					helper.body.append("<br/>");
				helper.body.append(part);
			}
			helper.body.hideWhenEmpty();
		} else {
			helper.title.html(title).show();
			helper.body.hide();
		}
		
		// if element has href or src, add and show it, otherwise hide it
		if( settings(this).showURL && $(this).url() )
			helper.url.html( $(this).url().replace('http://', '') ).show();
		else 
			helper.url.hide();
		
		// add an optional class for this tip
		helper.parent.addClass(settings(this).extraClass);

		// fix PNG background for IE
		if (settings(this).fixPNG )
			helper.parent.fixPNG();
			
		handle.apply(this, arguments);
	}
	
	// delete timeout and show helper
	function show() {
		tID = null;
		if ((!IE || !$.fn.bgiframe) && settings(current).fade) {
			if (helper.parent.is(":animated"))
				helper.parent.stop().show().fadeTo(settings(current).fade, current.tOpacity);
			else
				helper.parent.is(':visible') ? helper.parent.fadeTo(settings(current).fade, current.tOpacity) : helper.parent.fadeIn(settings(current).fade);
		} else {
			helper.parent.show();
		}
		update();
	}
	
	/**
	 * callback for mousemove
	 * updates the helper position
	 * removes itself when no current element
	 */
	function update(event)	{
		if($.tooltip.blocked)
			return;
		
		if (event && event.target.tagName == "OPTION") {
			return;
		}
		
		// stop updating when tracking is disabled and the tooltip is visible
		if ( !track && helper.parent.is(":visible")) {
			$(document.body).unbind('mousemove', update)
		}
		
		// if no current element is available, remove this listener
		if( current == null ) {
			$(document.body).unbind('mousemove', update);
			return;	
		}
		
		// remove position helper classes
		helper.parent.removeClass("viewport-right").removeClass("viewport-bottom");
		
		var left = helper.parent[0].offsetLeft;
		var top = helper.parent[0].offsetTop;
		if (event) {
			// position the helper 15 pixel to bottom right, starting from mouse position
			left = event.pageX + settings(current).left;
			top = event.pageY + settings(current).top;
			var right='auto';
			if (settings(current).positionLeft) {
				right = $(window).width() - left;
				left = 'auto';
			}
			helper.parent.css({
				left: left,
				right: right,
				top: top
			});
		}
		
		var v = viewport(),
			h = helper.parent[0];
		// check horizontal position
		if (v.x + v.cx < h.offsetLeft + h.offsetWidth) {
			left -= h.offsetWidth + 20 + settings(current).left;
			helper.parent.css({left: left + 'px'}).addClass("viewport-right");
		}
		// check vertical position
		if (v.y + v.cy < h.offsetTop + h.offsetHeight) {
			top -= h.offsetHeight + 20 + settings(current).top;
			helper.parent.css({top: top + 'px'}).addClass("viewport-bottom");
		}
	}
	
	function viewport() {
		return {
			x: $(window).scrollLeft(),
			y: $(window).scrollTop(),
			cx: $(window).width(),
			cy: $(window).height()
		};
	}
	
	// hide helper and restore added classes and the title
	function hide(event) {
		if($.tooltip.blocked)
			return;
		// clear timeout if possible
		if(tID)
			clearTimeout(tID);
		// no more current element
		current = null;
		
		var tsettings = settings(this);
		function complete() {
			helper.parent.removeClass( tsettings.extraClass ).hide().css("opacity", "");
		}
		if ((!IE || !$.fn.bgiframe) && tsettings.fade) {
			if (helper.parent.is(':animated'))
				helper.parent.stop().fadeTo(tsettings.fade, 0, complete);
			else
				helper.parent.stop().fadeOut(tsettings.fade, complete);
		} else
			complete();
		
		if( settings(this).fixPNG )
			helper.parent.unfixPNG();
	}
	
})(jQuery);

console.log('=============== >  jquery.tooltip.js ');

/*!
 * jQuery Form Plugin
 * version: 3.27.0-2013.02.06
 * @requires jQuery v1.5 or later
 *
 * Examples and documentation at: http://malsup.com/jquery/form/
 * Project repository: https://github.com/malsup/form
 * Dual licensed under the MIT and GPL licenses:
 *    http://malsup.github.com/mit-license.txt
 *    http://malsup.github.com/gpl-license-v2.txt
 */
/*global ActiveXObject alert */
;(function($) {
"use strict";

/*
    Usage Note:
    -----------
    Do not use both ajaxSubmit and ajaxForm on the same form.  These
    functions are mutually exclusive.  Use ajaxSubmit if you want
    to bind your own submit handler to the form.  For example,

    $(document).ready(function() {
        $('#myForm').on('submit', function(e) {
            e.preventDefault(); // <-- important
            $(this).ajaxSubmit({
                target: '#output'
            });
        });
    });

    Use ajaxForm when you want the plugin to manage all the event binding
    for you.  For example,

    $(document).ready(function() {
        $('#myForm').ajaxForm({
            target: '#output'
        });
    });

    You can also use ajaxForm with delegation (requires jQuery v1.7+), so the
    form does not have to exist when you invoke ajaxForm:

    $('#myForm').ajaxForm({
        delegation: true,
        target: '#output'
    });

    When using ajaxForm, the ajaxSubmit function will be invoked for you
    at the appropriate time.
*/

/**
 * Feature detection
 */
var feature = {};
feature.fileapi = $("<input type='file'/>").get(0).files !== undefined;
feature.formdata = window.FormData !== undefined;

/**
 * ajaxSubmit() provides a mechanism for immediately submitting
 * an HTML form using AJAX.
 */
$.fn.ajaxSubmit = function(options) {
    /*jshint scripturl:true */

    // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
    if (!this.length) {
        log('ajaxSubmit: skipping submit process - no element selected');
        return this;
    }

    var method, action, url, $form = this;

    if (typeof options == 'function') {
        options = { success: options };
    }

    method = this.attr('method');
    action = this.attr('action');
    url = (typeof action === 'string') ? $.trim(action) : '';
    url = url || window.location.href || '';
    if (url) {
        // clean url (don't include hash vaue)
        url = (url.match(/^([^#]+)/)||[])[1];
    }

    options = $.extend(true, {
        url:  url,
        success: $.ajaxSettings.success,
        type: method || 'GET',
        iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
    }, options);

    // hook for manipulating the form data before it is extracted;
    // convenient for use with rich editors like tinyMCE or FCKEditor
    var veto = {};
    this.trigger('form-pre-serialize', [this, options, veto]);
    if (veto.veto) {
        log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
        return this;
    }

    // provide opportunity to alter form data before it is serialized
    if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
        log('ajaxSubmit: submit aborted via beforeSerialize callback');
        return this;
    }

    var traditional = options.traditional;
    if ( traditional === undefined ) {
        traditional = $.ajaxSettings.traditional;
    }

    var elements = [];
    var qx, a = this.formToArray(options.semantic, elements);
    if (options.data) {
        options.extraData = options.data;
        qx = $.param(options.data, traditional);
    }

    // give pre-submit callback an opportunity to abort the submit
    if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
        log('ajaxSubmit: submit aborted via beforeSubmit callback');
        return this;
    }

    // fire vetoable 'validate' event
    this.trigger('form-submit-validate', [a, this, options, veto]);
    if (veto.veto) {
        log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
        return this;
    }

    var q = $.param(a, traditional);
    if (qx) {
        q = ( q ? (q + '&' + qx) : qx );
    }
    if (options.type.toUpperCase() == 'GET') {
        options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
        options.data = null;  // data is null for 'get'
    }
    else {
        options.data = q; // data is the query string for 'post'
    }

    var callbacks = [];
    if (options.resetForm) {
        callbacks.push(function() { $form.resetForm(); });
    }
    if (options.clearForm) {
        callbacks.push(function() { $form.clearForm(options.includeHidden); });
    }

    // perform a load on the target only if dataType is not provided
    if (!options.dataType && options.target) {
        var oldSuccess = options.success || function(){};
        callbacks.push(function(data) {
            var fn = options.replaceTarget ? 'replaceWith' : 'html';
            $(options.target)[fn](data).each(oldSuccess, arguments);
        });
    }
    else if (options.success) {
        callbacks.push(options.success);
    }

    options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
        var context = options.context || this ;    // jQuery 1.4+ supports scope context
        for (var i=0, max=callbacks.length; i < max; i++) {
            callbacks[i].apply(context, [data, status, xhr || $form, $form]);
        }
    };

    // are there files to upload?

    // [value] (issue #113), also see comment:
    // https://github.com/malsup/form/commit/588306aedba1de01388032d5f42a60159eea9228#commitcomment-2180219
    var fileInputs = $('input[type=file]:enabled[value!=""]', this);

    var hasFileInputs = fileInputs.length > 0;
    var mp = 'multipart/form-data';
    var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);

    var fileAPI = feature.fileapi && feature.formdata;
    log("fileAPI :" + fileAPI);
    var shouldUseFrame = (hasFileInputs || multipart) && !fileAPI;

    var jqxhr;

    // options.iframe allows user to force iframe mode
    // 06-NOV-09: now defaulting to iframe mode if file input is detected
    if (options.iframe !== false && (options.iframe || shouldUseFrame)) {
        // hack to fix Safari hang (thanks to Tim Molendijk for this)
        // see:  http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
        if (options.closeKeepAlive) {
            $.get(options.closeKeepAlive, function() {
                jqxhr = fileUploadIframe(a);
            });
        }
        else {
            jqxhr = fileUploadIframe(a);
        }
    }
    else if ((hasFileInputs || multipart) && fileAPI) {
        jqxhr = fileUploadXhr(a);
    }
    else {
        jqxhr = $.ajax(options);
    }

    $form.removeData('jqxhr').data('jqxhr', jqxhr);

    // clear element array
    for (var k=0; k < elements.length; k++)
        elements[k] = null;

    // fire 'notify' event
    this.trigger('form-submit-notify', [this, options]);
    return this;

    // utility fn for deep serialization
    function deepSerialize(extraData){
        var serialized = $.param(extraData).split('&');
        var len = serialized.length;
        var result = [];
        var i, part;
        for (i=0; i < len; i++) {
            // #252; undo param space replacement
            serialized[i] = serialized[i].replace(/\+/g,' ');
            part = serialized[i].split('=');
            // #278; use array instead of object storage, favoring array serializations
            result.push([decodeURIComponent(part[0]), decodeURIComponent(part[1])]);
        }
        return result;
    }

     // XMLHttpRequest Level 2 file uploads (big hat tip to francois2metz)
    function fileUploadXhr(a) {
        var formdata = new FormData();

        for (var i=0; i < a.length; i++) {
            formdata.append(a[i].name, a[i].value);
        }

        if (options.extraData) {
            var serializedData = deepSerialize(options.extraData);
            for (i=0; i < serializedData.length; i++)
                if (serializedData[i])
                    formdata.append(serializedData[i][0], serializedData[i][1]);
        }

        options.data = null;

        var s = $.extend(true, {}, $.ajaxSettings, options, {
            contentType: false,
            processData: false,
            cache: false,
            type: method || 'POST'
        });

        if (options.uploadProgress) {
            // workaround because jqXHR does not expose upload property
            s.xhr = function() {
                var xhr = jQuery.ajaxSettings.xhr();
                if (xhr.upload) {
                    xhr.upload.addEventListener('progress', function(event) {
                        var percent = 0;
                        var position = event.loaded || event.position; /*event.position is deprecated*/
                        var total = event.total;
                        if (event.lengthComputable) {
                            percent = Math.ceil(position / total * 100);
                        }
                        options.uploadProgress(event, position, total, percent);
                    }, false);
                }
                return xhr;
            };
        }

        s.data = null;
            var beforeSend = s.beforeSend;
            s.beforeSend = function(xhr, o) {
                o.data = formdata;
                if(beforeSend)
                    beforeSend.call(this, xhr, o);
        };
        return $.ajax(s);
    }

    // private function for handling file uploads (hat tip to YAHOO!)
    function fileUploadIframe(a) {
        var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle;
        var useProp = !!$.fn.prop;
        var deferred = $.Deferred();

        if (a) {
            // ensure that every serialized input is still enabled
            for (i=0; i < elements.length; i++) {
                el = $(elements[i]);
                if ( useProp )
                    el.prop('disabled', false);
                else
                    el.removeAttr('disabled');
            }
        }

        s = $.extend(true, {}, $.ajaxSettings, options);
        s.context = s.context || s;
        id = 'jqFormIO' + (new Date().getTime());
        if (s.iframeTarget) {
            $io = $(s.iframeTarget);
            n = $io.attr('name');
            if (!n)
                 $io.attr('name', id);
            else
                id = n;
        }
        else {
            $io = $('<iframe name="' + id + '" src="'+ s.iframeSrc +'" />');
            $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
        }
        io = $io[0];


        xhr = { // mock object
            aborted: 0,
            responseText: null,
            responseXML: null,
            status: 0,
            statusText: 'n/a',
            getAllResponseHeaders: function() {},
            getResponseHeader: function() {},
            setRequestHeader: function() {},
            abort: function(status) {
                var e = (status === 'timeout' ? 'timeout' : 'aborted');
                log('aborting upload... ' + e);
                this.aborted = 1;

                try { // #214, #257
                    if (io.contentWindow.document.execCommand) {
                        io.contentWindow.document.execCommand('Stop');
                    }
                }
                catch(ignore) {}

                $io.attr('src', s.iframeSrc); // abort op in progress
                xhr.error = e;
                if (s.error)
                    s.error.call(s.context, xhr, e, status);
                if (g)
                    $.event.trigger("ajaxError", [xhr, s, e]);
                if (s.complete)
                    s.complete.call(s.context, xhr, e);
            }
        };

        g = s.global;
        // trigger ajax global events so that activity/block indicators work like normal
        if (g && 0 === $.active++) {
            $.event.trigger("ajaxStart");
        }
        if (g) {
            $.event.trigger("ajaxSend", [xhr, s]);
        }

        if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
            if (s.global) {
                $.active--;
            }
            deferred.reject();
            return deferred;
        }
        if (xhr.aborted) {
            deferred.reject();
            return deferred;
        }

        // add submitting element to data if we know it
        sub = form.clk;
        if (sub) {
            n = sub.name;
            if (n && !sub.disabled) {
                s.extraData = s.extraData || {};
                s.extraData[n] = sub.value;
                if (sub.type == "image") {
                    s.extraData[n+'.x'] = form.clk_x;
                    s.extraData[n+'.y'] = form.clk_y;
                }
            }
        }

        var CLIENT_TIMEOUT_ABORT = 1;
        var SERVER_ABORT = 2;

        function getDoc(frame) {
            var doc = frame.contentWindow ? frame.contentWindow.document : frame.contentDocument ? frame.contentDocument : frame.document;
            return doc;
        }

        // Rails CSRF hack (thanks to Yvan Barthelemy)
        var csrf_token = $('meta[name=csrf-token]').attr('content');
        var csrf_param = $('meta[name=csrf-param]').attr('content');
        if (csrf_param && csrf_token) {
            s.extraData = s.extraData || {};
            s.extraData[csrf_param] = csrf_token;
        }

        // take a breath so that pending repaints get some cpu time before the upload starts
        function doSubmit() {
            // make sure form attrs are set
            var t = $form.attr('target'), a = $form.attr('action');

            // update form attrs in IE friendly way
            form.setAttribute('target',id);
            if (!method) {
                form.setAttribute('method', 'POST');
            }
            if (a != s.url) {
                form.setAttribute('action', s.url);
            }

            // ie borks in some cases when setting encoding
            if (! s.skipEncodingOverride && (!method || /post/i.test(method))) {
                $form.attr({
                    encoding: 'multipart/form-data',
                    enctype:  'multipart/form-data'
                });
            }

            // support timout
            if (s.timeout) {
                timeoutHandle = setTimeout(function() { timedOut = true; cb(CLIENT_TIMEOUT_ABORT); }, s.timeout);
            }

            // look for server aborts
            function checkState() {
                try {
                    var state = getDoc(io).readyState;
                    log('state = ' + state);
                    if (state && state.toLowerCase() == 'uninitialized')
                        setTimeout(checkState,50);
                }
                catch(e) {
                    log('Server abort: ' , e, ' (', e.name, ')');
                    cb(SERVER_ABORT);
                    if (timeoutHandle)
                        clearTimeout(timeoutHandle);
                    timeoutHandle = undefined;
                }
            }

            // add "extra" data to form if provided in options
            var extraInputs = [];
            try {
                if (s.extraData) {
                    for (var n in s.extraData) {
                        if (s.extraData.hasOwnProperty(n)) {
                           // if using the $.param format that allows for multiple values with the same name
                           if($.isPlainObject(s.extraData[n]) && s.extraData[n].hasOwnProperty('name') && s.extraData[n].hasOwnProperty('value')) {
                               extraInputs.push(
                               $('<input type="hidden" name="'+s.extraData[n].name+'">').val(s.extraData[n].value)
                                   .appendTo(form)[0]);
                           } else {
                               extraInputs.push(
                               $('<input type="hidden" name="'+n+'">').val(s.extraData[n])
                                   .appendTo(form)[0]);
                           }
                        }
                    }
                }

                if (!s.iframeTarget) {
                    // add iframe to doc and submit the form
                    $io.appendTo('body');
                    if (io.attachEvent)
                        io.attachEvent('onload', cb);
                    else
                        io.addEventListener('load', cb, false);
                }
                setTimeout(checkState,15);
                // just in case form has element with name/id of 'submit'
                var submitFn = document.createElement('form').submit;
                submitFn.apply(form);
            }
            finally {
                // reset attrs and remove "extra" input elements
                form.setAttribute('action',a);
                if(t) {
                    form.setAttribute('target', t);
                } else {
                    $form.removeAttr('target');
                }
                $(extraInputs).remove();
            }
        }

        if (s.forceSync) {
            doSubmit();
        }
        else {
            setTimeout(doSubmit, 10); // this lets dom updates render
        }

        var data, doc, domCheckCount = 50, callbackProcessed;

        function cb(e) {
            if (xhr.aborted || callbackProcessed) {
                return;
            }
            try {
                doc = getDoc(io);
            }
            catch(ex) {
                log('cannot access response document: ', ex);
                e = SERVER_ABORT;
            }
            if (e === CLIENT_TIMEOUT_ABORT && xhr) {
                xhr.abort('timeout');
                deferred.reject(xhr, 'timeout');
                return;
            }
            else if (e == SERVER_ABORT && xhr) {
                xhr.abort('server abort');
                deferred.reject(xhr, 'error', 'server abort');
                return;
            }

            if (!doc || doc.location.href == s.iframeSrc) {
                // response not received yet
                if (!timedOut)
                    return;
            }
            if (io.detachEvent)
                io.detachEvent('onload', cb);
            else
                io.removeEventListener('load', cb, false);

            var status = 'success', errMsg;
            try {
                if (timedOut) {
                    throw 'timeout';
                }

                var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
                log('isXml='+isXml);
                if (!isXml && window.opera && (doc.body === null || !doc.body.innerHTML)) {
                    if (--domCheckCount) {
                        // in some browsers (Opera) the iframe DOM is not always traversable when
                        // the onload callback fires, so we loop a bit to accommodate
                        log('requeing onLoad callback, DOM not available');
                        setTimeout(cb, 250);
                        return;
                    }
                    // let this fall through because server response could be an empty document
                    //log('Could not access iframe DOM after mutiple tries.');
                    //throw 'DOMException: not available';
                }

                //log('response detected');
                var docRoot = doc.body ? doc.body : doc.documentElement;
                xhr.responseText = docRoot ? docRoot.innerHTML : null;
                xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
                if (isXml)
                    s.dataType = 'xml';
                xhr.getResponseHeader = function(header){
                    var headers = {'content-type': s.dataType};
                    return headers[header];
                };
                // support for XHR 'status' & 'statusText' emulation :
                if (docRoot) {
                    xhr.status = Number( docRoot.getAttribute('status') ) || xhr.status;
                    xhr.statusText = docRoot.getAttribute('statusText') || xhr.statusText;
                }

                var dt = (s.dataType || '').toLowerCase();
                var scr = /(json|script|text)/.test(dt);
                if (scr || s.textarea) {
                    // see if user embedded response in textarea
                    var ta = doc.getElementsByTagName('textarea')[0];
                    if (ta) {
                        xhr.responseText = ta.value;
                        // support for XHR 'status' & 'statusText' emulation :
                        xhr.status = Number( ta.getAttribute('status') ) || xhr.status;
                        xhr.statusText = ta.getAttribute('statusText') || xhr.statusText;
                    }
                    else if (scr) {
                        // account for browsers injecting pre around json response
                        var pre = doc.getElementsByTagName('pre')[0];
                        var b = doc.getElementsByTagName('body')[0];
                        if (pre) {
                            xhr.responseText = pre.textContent ? pre.textContent : pre.innerText;
                        }
                        else if (b) {
                            xhr.responseText = b.textContent ? b.textContent : b.innerText;
                        }
                    }
                }
                else if (dt == 'xml' && !xhr.responseXML && xhr.responseText) {
                    xhr.responseXML = toXml(xhr.responseText);
                }

                try {
                    data = httpData(xhr, dt, s);
                }
                catch (e) {
                    status = 'parsererror';
                    xhr.error = errMsg = (e || status);
                }
            }
            catch (e) {
                log('error caught: ',e);
                status = 'error';
                xhr.error = errMsg = (e || status);
            }

            if (xhr.aborted) {
                log('upload aborted');
                status = null;
            }

            if (xhr.status) { // we've set xhr.status
                status = (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) ? 'success' : 'error';
            }

            // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
            if (status === 'success') {
                if (s.success)
                    s.success.call(s.context, data, 'success', xhr);
                deferred.resolve(xhr.responseText, 'success', xhr);
                if (g)
                    $.event.trigger("ajaxSuccess", [xhr, s]);
            }
            else if (status) {
                if (errMsg === undefined)
                    errMsg = xhr.statusText;
                if (s.error)
                    s.error.call(s.context, xhr, status, errMsg);
                deferred.reject(xhr, 'error', errMsg);
                if (g)
                    $.event.trigger("ajaxError", [xhr, s, errMsg]);
            }

            if (g)
                $.event.trigger("ajaxComplete", [xhr, s]);

            if (g && ! --$.active) {
                $.event.trigger("ajaxStop");
            }

            if (s.complete)
                s.complete.call(s.context, xhr, status);

            callbackProcessed = true;
            if (s.timeout)
                clearTimeout(timeoutHandle);

            // clean up
            setTimeout(function() {
                if (!s.iframeTarget)
                    $io.remove();
                xhr.responseXML = null;
            }, 100);
        }

        var toXml = $.parseXML || function(s, doc) { // use parseXML if available (jQuery 1.5+)
            if (window.ActiveXObject) {
                doc = new ActiveXObject('Microsoft.XMLDOM');
                doc.async = 'false';
                doc.loadXML(s);
            }
            else {
                doc = (new DOMParser()).parseFromString(s, 'text/xml');
            }
            return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror') ? doc : null;
        };
        var parseJSON = $.parseJSON || function(s) {
            /*jslint evil:true */
            return window['eval']('(' + s + ')');
        };

        var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4

            var ct = xhr.getResponseHeader('content-type') || '',
                xml = type === 'xml' || !type && ct.indexOf('xml') >= 0,
                data = xml ? xhr.responseXML : xhr.responseText;

            if (xml && data.documentElement.nodeName === 'parsererror') {
                if ($.error)
                    $.error('parsererror');
            }
            if (s && s.dataFilter) {
                data = s.dataFilter(data, type);
            }
            if (typeof data === 'string') {
                if (type === 'json' || !type && ct.indexOf('json') >= 0) {
                    data = parseJSON(data);
                } else if (type === "script" || !type && ct.indexOf("javascript") >= 0) {
                    $.globalEval(data);
                }
            }
            return data;
        };

        return deferred;
    }
};

/**
 * ajaxForm() provides a mechanism for fully automating form submission.
 *
 * The advantages of using this method instead of ajaxSubmit() are:
 *
 * 1: This method will include coordinates for <input type="image" /> elements (if the element
 *    is used to submit the form).
 * 2. This method will include the submit element's name/value data (for the element that was
 *    used to submit the form).
 * 3. This method binds the submit() method to the form for you.
 *
 * The options argument for ajaxForm works exactly as it does for ajaxSubmit.  ajaxForm merely
 * passes the options argument along after properly binding events for submit elements and
 * the form itself.
 */
$.fn.ajaxForm = function(options) {
    options = options || {};
    options.delegation = options.delegation && $.isFunction($.fn.on);

    // in jQuery 1.3+ we can fix mistakes with the ready state
    if (!options.delegation && this.length === 0) {
        var o = { s: this.selector, c: this.context };
        if (!$.isReady && o.s) {
            log('DOM not ready, queuing ajaxForm');
            $(function() {
                $(o.s,o.c).ajaxForm(options);
            });
            return this;
        }
        // is your DOM ready?  http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
        log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)'));
        return this;
    }

    if ( options.delegation ) {
        $(document)
            .off('submit.form-plugin', this.selector, doAjaxSubmit)
            .off('click.form-plugin', this.selector, captureSubmittingElement)
            .on('submit.form-plugin', this.selector, options, doAjaxSubmit)
            .on('click.form-plugin', this.selector, options, captureSubmittingElement);
        return this;
    }

    return this.ajaxFormUnbind()
        .bind('submit.form-plugin', options, doAjaxSubmit)
        .bind('click.form-plugin', options, captureSubmittingElement);
};

// private event handlers
function doAjaxSubmit(e) {
    /*jshint validthis:true */
    var options = e.data;
    if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed
        e.preventDefault();
        $(this).ajaxSubmit(options);
    }
}

function captureSubmittingElement(e) {
    /*jshint validthis:true */
    var target = e.target;
    var $el = $(target);
    if (!($el.is("[type=submit],[type=image]"))) {
        // is this a child element of the submit el?  (ex: a span within a button)
        var t = $el.closest('[type=submit]');
        if (t.length === 0) {
            return;
        }
        target = t[0];
    }
    var form = this;
    form.clk = target;
    if (target.type == 'image') {
        if (e.offsetX !== undefined) {
            form.clk_x = e.offsetX;
            form.clk_y = e.offsetY;
        } else if (typeof $.fn.offset == 'function') {
            var offset = $el.offset();
            form.clk_x = e.pageX - offset.left;
            form.clk_y = e.pageY - offset.top;
        } else {
            form.clk_x = e.pageX - target.offsetLeft;
            form.clk_y = e.pageY - target.offsetTop;
        }
    }
    // clear form vars
    setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
}


// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
$.fn.ajaxFormUnbind = function() {
    return this.unbind('submit.form-plugin click.form-plugin');
};

/**
 * formToArray() gathers form element data into an array of objects that can
 * be passed to any of the following ajax functions: $.get, $.post, or load.
 * Each object in the array has both a 'name' and 'value' property.  An example of
 * an array for a simple login form might be:
 *
 * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
 *
 * It is this array that is passed to pre-submit callback functions provided to the
 * ajaxSubmit() and ajaxForm() methods.
 */
$.fn.formToArray = function(semantic, elements) {
    var a = [];
    if (this.length === 0) {
        return a;
    }

    var form = this[0];
    var els = semantic ? form.getElementsByTagName('*') : form.elements;
    if (!els) {
        return a;
    }

    var i,j,n,v,el,max,jmax;
    for(i=0, max=els.length; i < max; i++) {
        el = els[i];
        n = el.name;
        if (!n) {
            continue;
        }

        if (semantic && form.clk && el.type == "image") {
            // handle image inputs on the fly when semantic == true
            if(!el.disabled && form.clk == el) {
                a.push({name: n, value: $(el).val(), type: el.type });
                a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
            }
            continue;
        }

        v = $.fieldValue(el, true);
        if (v && v.constructor == Array) {
            if (elements)
                elements.push(el);
            for(j=0, jmax=v.length; j < jmax; j++) {
                a.push({name: n, value: v[j]});
            }
        }
        else if (feature.fileapi && el.type == 'file' && !el.disabled) {
            if (elements)
                elements.push(el);
            var files = el.files;
            if (files.length) {
                for (j=0; j < files.length; j++) {
                    a.push({name: n, value: files[j], type: el.type});
                }
            }
            else {
                // #180
                a.push({ name: n, value: '', type: el.type });
            }
        }
        else if (v !== null && typeof v != 'undefined') {
            if (elements)
                elements.push(el);
            a.push({name: n, value: v, type: el.type, required: el.required});
        }
    }

    if (!semantic && form.clk) {
        // input type=='image' are not found in elements array! handle it here
        var $input = $(form.clk), input = $input[0];
        n = input.name;
        if (n && !input.disabled && input.type == 'image') {
            a.push({name: n, value: $input.val()});
            a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
        }
    }
    return a;
};

/**
 * Serializes form data into a 'submittable' string. This method will return a string
 * in the format: name1=value1&amp;name2=value2
 */
$.fn.formSerialize = function(semantic) {
    //hand off to jQuery.param for proper encoding
    return $.param(this.formToArray(semantic));
};

/**
 * Serializes all field elements in the jQuery object into a query string.
 * This method will return a string in the format: name1=value1&amp;name2=value2
 */
$.fn.fieldSerialize = function(successful) {
    var a = [];
    this.each(function() {
        var n = this.name;
        if (!n) {
            return;
        }
        var v = $.fieldValue(this, successful);
        if (v && v.constructor == Array) {
            for (var i=0,max=v.length; i < max; i++) {
                a.push({name: n, value: v[i]});
            }
        }
        else if (v !== null && typeof v != 'undefined') {
            a.push({name: this.name, value: v});
        }
    });
    //hand off to jQuery.param for proper encoding
    return $.param(a);
};

/**
 * Returns the value(s) of the element in the matched set.  For example, consider the following form:
 *
 *  <form><fieldset>
 *      <input name="A" type="text" />
 *      <input name="A" type="text" />
 *      <input name="B" type="checkbox" value="B1" />
 *      <input name="B" type="checkbox" value="B2"/>
 *      <input name="C" type="radio" value="C1" />
 *      <input name="C" type="radio" value="C2" />
 *  </fieldset></form>
 *
 *  var v = $('input[type=text]').fieldValue();
 *  // if no values are entered into the text inputs
 *  v == ['','']
 *  // if values entered into the text inputs are 'foo' and 'bar'
 *  v == ['foo','bar']
 *
 *  var v = $('input[type=checkbox]').fieldValue();
 *  // if neither checkbox is checked
 *  v === undefined
 *  // if both checkboxes are checked
 *  v == ['B1', 'B2']
 *
 *  var v = $('input[type=radio]').fieldValue();
 *  // if neither radio is checked
 *  v === undefined
 *  // if first radio is checked
 *  v == ['C1']
 *
 * The successful argument controls whether or not the field element must be 'successful'
 * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
 * The default value of the successful argument is true.  If this value is false the value(s)
 * for each element is returned.
 *
 * Note: This method *always* returns an array.  If no valid value can be determined the
 *    array will be empty, otherwise it will contain one or more values.
 */
$.fn.fieldValue = function(successful) {
    for (var val=[], i=0, max=this.length; i < max; i++) {
        var el = this[i];
        var v = $.fieldValue(el, successful);
        if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) {
            continue;
        }
        if (v.constructor == Array)
            $.merge(val, v);
        else
            val.push(v);
    }
    return val;
};

/**
 * Returns the value of the field element.
 */
$.fieldValue = function(el, successful) {
    var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
    if (successful === undefined) {
        successful = true;
    }

    if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
        (t == 'checkbox' || t == 'radio') && !el.checked ||
        (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
        tag == 'select' && el.selectedIndex == -1)) {
            return null;
    }

    if (tag == 'select') {
        var index = el.selectedIndex;
        if (index < 0) {
            return null;
        }
        var a = [], ops = el.options;
        var one = (t == 'select-one');
        var max = (one ? index+1 : ops.length);
        for(var i=(one ? index : 0); i < max; i++) {
            var op = ops[i];
            if (op.selected) {
                var v = op.value;
                if (!v) { // extra pain for IE...
                    v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
                }
                if (one) {
                    return v;
                }
                a.push(v);
            }
        }
        return a;
    }
    return $(el).val();
};

/**
 * Clears the form data.  Takes the following actions on the form's input fields:
 *  - input text fields will have their 'value' property set to the empty string
 *  - select elements will have their 'selectedIndex' property set to -1
 *  - checkbox and radio inputs will have their 'checked' property set to false
 *  - inputs of type submit, button, reset, and hidden will *not* be effected
 *  - button elements will *not* be effected
 */
$.fn.clearForm = function(includeHidden) {
    return this.each(function() {
        $('input,select,textarea', this).clearFields(includeHidden);
    });
};

/**
 * Clears the selected form elements.
 */
$.fn.clearFields = $.fn.clearInputs = function(includeHidden) {
    var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list
    return this.each(function() {
        var t = this.type, tag = this.tagName.toLowerCase();
        if (re.test(t) || tag == 'textarea') {
            this.value = '';
        }
        else if (t == 'checkbox' || t == 'radio') {
            this.checked = false;
        }
        else if (tag == 'select') {
            this.selectedIndex = -1;
        }
		else if (t == "file") {
			if (/MSIE/.test(navigator.userAgent)) {
				$(this).replaceWith($(this).clone());
			} else {
				$(this).val('');
			}
		}
        else if (includeHidden) {
            // includeHidden can be the value true, or it can be a selector string
            // indicating a special test; for example:
            //  $('#myForm').clearForm('.special:hidden')
            // the above would clean hidden inputs that have the class of 'special'
            if ( (includeHidden === true && /hidden/.test(t)) ||
                 (typeof includeHidden == 'string' && $(this).is(includeHidden)) )
                this.value = '';
        }
    });
};

/**
 * Resets the form data.  Causes all form elements to be reset to their original value.
 */
$.fn.resetForm = function() {
    return this.each(function() {
        // guard against an input with the name of 'reset'
        // note that IE reports the reset function as an 'object'
        if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) {
            this.reset();
        }
    });
};

/**
 * Enables or disables any matching elements.
 */
$.fn.enable = function(b) {
    if (b === undefined) {
        b = true;
    }
    return this.each(function() {
        this.disabled = !b;
    });
};

/**
 * Checks/unchecks any matching checkboxes or radio buttons and
 * selects/deselects and matching option elements.
 */
$.fn.selected = function(select) {
    if (select === undefined) {
        select = true;
    }
    return this.each(function() {
        var t = this.type;
        if (t == 'checkbox' || t == 'radio') {
            this.checked = select;
        }
        else if (this.tagName.toLowerCase() == 'option') {
            var $sel = $(this).parent('select');
            if (select && $sel[0] && $sel[0].type == 'select-one') {
                // deselect all other options
                $sel.find('option').selected(false);
            }
            this.selected = select;
        }
    });
};

// expose debug var
$.fn.ajaxSubmit.debug = false;

// helper fn for console logging
function log() {
    if (!$.fn.ajaxSubmit.debug)
        return;
    var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
    if (window.console && window.console.log) {
        window.console.log(msg);
    }
    else if (window.opera && window.opera.postError) {
        window.opera.postError(msg);
    }
}

})(jQuery);

console.log('=============== >  jquery.form.js ');

function AstroMath(){}function Projection(t,e){this.PROJECTION=Projection.PROJ_TAN,this.ROT=this.tr_oR(t,e)}function Coo(t,e,i){this.lon=t,this.lat=e,this.prec=i,this.frame=null,this.computeDirCos()}function Tokenizer(t,e){this.string=Strings.trim(t,e),this.sep=e,this.pos=0}function Strings(){}function Numbers(){}function relMouseCoords(t){if(t.offsetX)return{x:t.offsetX,y:t.offsetY};if(!Utils.cssScale){var e=window.getComputedStyle(document.body,null),i=e.getPropertyValue("-webkit-transform")||e.getPropertyValue("-moz-transform")||e.getPropertyValue("-ms-transform")||e.getPropertyValue("-o-transform")||e.getPropertyValue("transform"),o=/matrix\((-?\d*\.?\d+),\s*0,\s*0,\s*(-?\d*\.?\d+),\s*0,\s*0\)/,r=i.match(o);Utils.cssScale=r?parseFloat(r[1]):1}var s=t;s.target;var a=s.target||s.srcElement,n=a.currentStyle||window.getComputedStyle(a,null),h=parseInt(n.borderLeftWidth,10),l=parseInt(n.borderTopWidth,10),c=a.getBoundingClientRect(),u=s.clientX-h-c.left,d=s.clientY-l-c.top;return{x:parseInt(u/Utils.cssScale),y:parseInt(d/Utils.cssScale)}}var cds=cds||{},A=A||{};"object"!=typeof JSON&&(JSON={}),function(){"use strict";function f(t){return 10>t?"0"+t:t}function quote(t){return escapable.lastIndex=0,escapable.test(t)?'"'+t.replace(escapable,function(t){var e=meta[t];return"string"==typeof e?e:"\\u"+("0000"+t.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+t+'"'}function str(t,e){var i,o,r,s,a,n=gap,h=e[t];switch(h&&"object"==typeof h&&"function"==typeof h.toJSON&&(h=h.toJSON(t)),"function"==typeof rep&&(h=rep.call(e,t,h)),typeof h){case"string":return quote(h);case"number":return isFinite(h)?h+"":"null";case"boolean":case"null":return h+"";case"object":if(!h)return"null";if(gap+=indent,a=[],"[object Array]"===Object.prototype.toString.apply(h)){for(s=h.length,i=0;s>i;i+=1)a[i]=str(i,h)||"null";return r=0===a.length?"[]":gap?"[\n"+gap+a.join(",\n"+gap)+"\n"+n+"]":"["+a.join(",")+"]",gap=n,r}if(rep&&"object"==typeof rep)for(s=rep.length,i=0;s>i;i+=1)"string"==typeof rep[i]&&(o=rep[i],r=str(o,h),r&&a.push(quote(o)+(gap?": ":":")+r));else for(o in h)Object.prototype.hasOwnProperty.call(h,o)&&(r=str(o,h),r&&a.push(quote(o)+(gap?": ":":")+r));return r=0===a.length?"{}":gap?"{\n"+gap+a.join(",\n"+gap)+"\n"+n+"}":"{"+a.join(",")+"}",gap=n,r}}"function"!=typeof Date.prototype.toJSON&&(Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null},String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(){return this.valueOf()});var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","	":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;"function"!=typeof JSON.stringify&&(JSON.stringify=function(t,e,i){var o;if(gap="",indent="","number"==typeof i)for(o=0;i>o;o+=1)indent+=" ";else"string"==typeof i&&(indent=i);if(rep=e,e&&"function"!=typeof e&&("object"!=typeof e||"number"!=typeof e.length))throw Error("JSON.stringify");return str("",{"":t})}),"function"!=typeof JSON.parse&&(JSON.parse=function(text,reviver){function walk(t,e){var i,o,r=t[e];if(r&&"object"==typeof r)for(i in r)Object.prototype.hasOwnProperty.call(r,i)&&(o=walk(r,i),void 0!==o?r[i]=o:delete r[i]);return reviver.call(t,e,r)}var j;if(text+="",cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(t){return"\\u"+("0000"+t.charCodeAt(0).toString(16)).slice(-4)})),/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return j=eval("("+text+")"),"function"==typeof reviver?walk({"":j},""):j;throw new SyntaxError("JSON.parse")})}(),Logger={},Logger.log=function(t,e){try{var i="http://alasky.u-strasbg.fr/cgi/AladinLiteLogger/log.py",o="";e&&(o=JSON.stringify(e)),$.ajax({url:i,data:{action:t,params:o,pageUrl:window.location.href,referer:document.referrer?document.referrer:""},method:"GET",dataType:"json"})}catch(r){window.console&&console.log("Exception: "+r)}},function(t){"function"==typeof define&&define.amd?define(["jquery"],t):"object"==typeof exports?module.exports=t:t(jQuery)}(function(t){function e(e){var r,s=e||window.event,a=[].slice.call(arguments,1),n=0,h=0,l=0,c=0,u=0;return e=t.event.fix(s),e.type="mousewheel",s.wheelDelta&&(n=s.wheelDelta),s.detail&&(n=-1*s.detail),l=n,void 0!==s.axis&&s.axis===s.HORIZONTAL_AXIS&&(l=0,h=-1*n),s.deltaY&&(l=-1*s.deltaY,n=l),s.deltaX&&(h=s.deltaX,n=-1*h),void 0!==s.wheelDeltaY&&(l=s.wheelDeltaY),void 0!==s.wheelDeltaX&&(h=-1*s.wheelDeltaX),c=Math.abs(n),(!i||i>c)&&(i=c),u=Math.max(Math.abs(l),Math.abs(h)),(!o||o>u)&&(o=u),r=n>0?"floor":"ceil",n=Math[r](n/i),h=Math[r](h/o),l=Math[r](l/o),a.unshift(e,n,h,l),(t.event.dispatch||t.event.handle).apply(this,a)}var i,o,r=["wheel","mousewheel","DOMMouseScroll","MozMousePixelScroll"],s="onwheel"in document||document.documentMode>=9?["wheel"]:["mousewheel","DomMouseScroll","MozMousePixelScroll"];if(t.event.fixHooks)for(var a=r.length;a;)t.event.fixHooks[r[--a]]=t.event.mouseHooks;t.event.special.mousewheel={setup:function(){if(this.addEventListener)for(var t=s.length;t;)this.addEventListener(s[--t],e,!1);else this.onmousewheel=e},teardown:function(){if(this.removeEventListener)for(var t=s.length;t;)this.removeEventListener(s[--t],e,!1);else this.onmousewheel=null}},t.fn.extend({mousewheel:function(t){return t?this.bind("mousewheel",t):this.trigger("mousewheel")},unmousewheel:function(t){return this.unbind("mousewheel",t)}})}),window.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){window.setTimeout(t,1e3/60)}}();var Stats=function(){function t(t,e,i){var o,r,s;for(r=0;30>r;r++)for(o=0;73>o;o++)s=4*(o+74*r),t[s]=t[s+4],t[s+1]=t[s+5],t[s+2]=t[s+6];for(r=0;30>r;r++)s=4*(73+74*r),e>r?(t[s]=O[i].bg.r,t[s+1]=O[i].bg.g,t[s+2]=O[i].bg.b):(t[s]=O[i].fg.r,t[s+1]=O[i].fg.g,t[s+2]=O[i].fg.b)}var e,i,o,r,s,a,n,h,l,c,u,d,p,f,v=0,g=2,m=0,y=(new Date).getTime(),w=y,C=y,x=0,S=1e3,M=0,b=0,T=1e3,I=0,P=0,R=1e3,A=0,O={fps:{bg:{r:16,g:16,b:48},fg:{r:0,g:255,b:255}},ms:{bg:{r:16,g:48,b:16},fg:{r:0,g:255,b:0}},mb:{bg:{r:48,g:16,b:26},fg:{r:255,g:0,b:128}}};e=document.createElement("div"),e.style.cursor="pointer",e.style.width="80px",e.style.opacity="0.9",e.style.zIndex="10001",e.addEventListener("click",function(){switch(v++,v==g&&(v=0),i.style.display="none",n.style.display="none",u.style.display="none",v){case 0:i.style.display="block";break;case 1:n.style.display="block";break;case 2:u.style.display="block"}},!1),i=document.createElement("div"),i.style.backgroundColor="rgb("+Math.floor(O.fps.bg.r/2)+","+Math.floor(O.fps.bg.g/2)+","+Math.floor(O.fps.bg.b/2)+")",i.style.padding="2px 0px 3px 0px",e.appendChild(i),o=document.createElement("div"),o.style.fontFamily="Helvetica, Arial, sans-serif",o.style.textAlign="left",o.style.fontSize="9px",o.style.color="rgb("+O.fps.fg.r+","+O.fps.fg.g+","+O.fps.fg.b+")",o.style.margin="0px 0px 1px 3px",o.innerHTML='<span style="font-weight:bold">FPS</span>',i.appendChild(o),r=document.createElement("canvas"),r.width=74,r.height=30,r.style.display="block",r.style.marginLeft="3px",i.appendChild(r),s=r.getContext("2d"),s.fillStyle="rgb("+O.fps.bg.r+","+O.fps.bg.g+","+O.fps.bg.b+")",s.fillRect(0,0,r.width,r.height),a=s.getImageData(0,0,r.width,r.height),n=document.createElement("div"),n.style.backgroundColor="rgb("+Math.floor(O.ms.bg.r/2)+","+Math.floor(O.ms.bg.g/2)+","+Math.floor(O.ms.bg.b/2)+")",n.style.padding="2px 0px 3px 0px",n.style.display="none",e.appendChild(n),h=document.createElement("div"),h.style.fontFamily="Helvetica, Arial, sans-serif",h.style.textAlign="left",h.style.fontSize="9px",h.style.color="rgb("+O.ms.fg.r+","+O.ms.fg.g+","+O.ms.fg.b+")",h.style.margin="0px 0px 1px 3px",h.innerHTML='<span style="font-weight:bold">MS</span>',n.appendChild(h),r=document.createElement("canvas"),r.width=74,r.height=30,r.style.display="block",r.style.marginLeft="3px",n.appendChild(r),l=r.getContext("2d"),l.fillStyle="rgb("+O.ms.bg.r+","+O.ms.bg.g+","+O.ms.bg.b+")",l.fillRect(0,0,r.width,r.height),c=l.getImageData(0,0,r.width,r.height);try{performance&&performance.memory&&performance.memory.totalJSHeapSize&&(g=3)}catch(E){}return u=document.createElement("div"),u.style.backgroundColor="rgb("+Math.floor(O.mb.bg.r/2)+","+Math.floor(O.mb.bg.g/2)+","+Math.floor(O.mb.bg.b/2)+")",u.style.padding="2px 0px 3px 0px",u.style.display="none",e.appendChild(u),d=document.createElement("div"),d.style.fontFamily="Helvetica, Arial, sans-serif",d.style.textAlign="left",d.style.fontSize="9px",d.style.color="rgb("+O.mb.fg.r+","+O.mb.fg.g+","+O.mb.fg.b+")",d.style.margin="0px 0px 1px 3px",d.innerHTML='<span style="font-weight:bold">MB</span>',u.appendChild(d),r=document.createElement("canvas"),r.width=74,r.height=30,r.style.display="block",r.style.marginLeft="3px",u.appendChild(r),p=r.getContext("2d"),p.fillStyle="#301010",p.fillRect(0,0,r.width,r.height),f=p.getImageData(0,0,r.width,r.height),{domElement:e,update:function(){m++,y=(new Date).getTime(),b=y-w,T=Math.min(T,b),I=Math.max(I,b),t(c.data,Math.min(30,30-30*(b/200)),"ms"),h.innerHTML='<span style="font-weight:bold">'+b+" MS</span> ("+T+"-"+I+")",l.putImageData(c,0,0),w=y,y>C+1e3&&(x=Math.round(1e3*m/(y-C)),S=Math.min(S,x),M=Math.max(M,x),t(a.data,Math.min(30,30-30*(x/100)),"fps"),o.innerHTML='<span style="font-weight:bold">'+x+" FPS</span> ("+S+"-"+M+")",s.putImageData(a,0,0),3==g&&(P=9.54e-7*performance.memory.usedJSHeapSize,R=Math.min(R,P),A=Math.max(A,P),t(f.data,Math.min(30,30-P/2),"mb"),d.innerHTML='<span style="font-weight:bold">'+Math.round(P)+" MB</span> ("+Math.round(R)+"-"+Math.round(A)+")",p.putImageData(f,0,0)),C=y,m=0)}}};Constants={},Constants.PI=Math.PI,Constants.C_PR=Math.PI/180,Constants.VLEV=2,Constants.EPS=1e-7,Constants.c=.105,Constants.LN10=Math.log(10),Constants.PIOVER2=Math.PI/2,Constants.TWOPI=2*Math.PI,Constants.TWOTHIRD=2/3,Constants.ARCSECOND_RADIAN=484813681109536e-20,SpatialVector=function(){function t(t,e,i){"use strict";this.x=t,this.y=e,this.z=i,this.ra_=0,this.dec_=0,this.okRaDec_=!1}return t.prototype.setXYZ=function(t,e,i){this.x=t,this.y=e,this.z=i,this.okRaDec_=!1},t.prototype.length=function(){"use strict";return Math.sqrt(this.lengthSquared())},t.prototype.lengthSquared=function(){"use strict";return this.x*this.x+this.y*this.y+this.z*this.z},t.prototype.normalized=function(){"use strict";var t=this.length();this.x/=t,this.y/=t,this.z/=t},t.prototype.set=function(t,e){"use strict";this.ra_=t,this.dec_=e,this.okRaDec_=!0,this.updateXYZ()},t.prototype.angle=function(t){"use strict";var e=this.y*t.z-this.z*t.y,i=this.z*t.x-this.x*t.z,o=this.x*t.y-this.y*t.x,r=Math.sqrt(e*e+i*i+o*o);return Math.abs(Math.atan2(r,dot(t)))},t.prototype.get=function(){"use strict";return[x,y,z]},t.prototype.toString=function(){"use strict";return"SpatialVector["+this.x+", "+this.y+", "+this.z+"]"},t.prototype.cross=function(e){"use strict";return new t(this.y*e.z-e.y*this.z,this.z*e.x-e.z*this.x,this.x*e.y-e.x()*this.y)},t.prototype.equal=function(t){"use strict";return this.x==t.x&&this.y==t.y&&this.z==t.z()?!0:!1},t.prototype.mult=function(e){"use strict";return new t(e*this.x,e*this.y,e*this.z)},t.prototype.dot=function(t){"use strict";return this.x*t.x+this.y*t.y+this.z*t.z},t.prototype.add=function(e){"use strict";return new t(this.x+e.x,this.y+e.y,this.z+e.z)},t.prototype.sub=function(e){"use strict";return new t(this.x-e.x,this.y-e.y,this.z-e.z)},t.prototype.dec=function(){"use strict";return this.okRaDec_||(this.normalized(),this.updateRaDec()),this.dec_},t.prototype.ra=function(){"use strict";return this.okRaDec_||(this.normalized(),this.updateRaDec()),this.ra_},t.prototype.updateXYZ=function(){"use strict";var t=Math.cos(this.dec_*Constants.C_PR);this.x=Math.cos(this.ra_*Constants.C_PR)*t,this.y=Math.sin(this.ra_*Constants.C_PR)*t,this.z=Math.sin(this.dec_*Constants.C_PR)},t.prototype.updateRaDec=function(){"use strict";this.dec_=Math.asin(this.z)/Constants.C_PR;var t=Math.cos(this.dec_*Constants.C_PR);this.ra_=t>Constants.EPS||-Constants.EPS>t?this.y>Constants.EPS||this.y<-Constants.EPS?0>this.y?360-Math.acos(this.x/t)/Constants.C_PR:Math.acos(this.x/t)/Constants.C_PR:0>this.x?180:0:0,this.okRaDec_=!0},t.prototype.toRaRadians=function(){"use strict";var t=0;return(0!=this.x||0!=this.y)&&(t=Math.atan2(this.y,this.x)),0>t&&(t+=2*Math.PI),t},t.prototype.toDeRadians=function(){var t=z/this.length(),e=Math.acos(t);return Math.PI/2-e},t}(),AngularPosition=function(){return AngularPosition=function(t,e){"use strict";this.theta=t,this.phi=e},AngularPosition.prototype.toString=function(){"use strict";return"theta: "+this.theta+", phi: "+this.phi},AngularPosition}(),LongRangeSetBuilder=function(){function t(){this.items=[]}return t.prototype.appendRange=function(t,e){for(var i=t;e>=i;i++)i in this.items||this.items.push(i)},t}(),HealpixIndex=function(){function t(t){"use strict";this.nside=t}return t.NS_MAX=8192,t.ORDER_MAX=13,t.NSIDELIST=[1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192],t.JRLL=[2,2,2,2,3,3,3,3,4,4,4,4],t.JPLL=[1,3,5,7,0,2,4,6,1,3,5,7],t.XOFFSET=[-1,-1,0,1,1,1,0,-1],t.YOFFSET=[0,1,1,1,0,-1,-1,-1],t.FACEARRAY=[[8,9,10,11,-1,-1,-1,-1,10,11,8,9],[5,6,7,4,8,9,10,11,9,10,11,8],[-1,-1,-1,-1,5,6,7,4,-1,-1,-1,-1],[4,5,6,7,11,8,9,10,11,8,9,10],[0,1,2,3,4,5,6,7,8,9,10,11],[1,2,3,0,0,1,2,3,5,6,7,4],[-1,-1,-1,-1,7,4,5,6,-1,-1,-1,-1],[3,0,1,2,3,0,1,2,4,5,6,7],[2,3,0,1,-1,-1,-1,-1,0,1,2,3]],t.SWAPARRAY=[[0,0,0,0,0,0,0,0,3,3,3,3],[0,0,0,0,0,0,0,0,6,6,6,6],[0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,5,5,5,5],[0,0,0,0,0,0,0,0,0,0,0,0],[5,5,5,5,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0],[6,6,6,6,0,0,0,0,0,0,0,0],[3,3,3,3,0,0,0,0,0,0,0,0]],t.Z0=Constants.TWOTHIRD,t.prototype.init=function(){"use strict";var e=256;this.ctab=Array(e),this.utab=Array(e);for(var i=0;256>i;++i)this.ctab[i]=1&i|(2&i)<<7|(4&i)>>1|(8&i)<<6|(16&i)>>2|(32&i)<<5|(64&i)>>3|(128&i)<<4,this.utab[i]=1&i|(2&i)<<1|(4&i)<<2|(8&i)<<3|(16&i)<<4|(32&i)<<5|(64&i)<<6|(128&i)<<7;this.nl2=2*this.nside,this.nl3=3*this.nside,this.nl4=4*this.nside,this.npface=this.nside*this.nside,this.ncap=2*this.nside*(this.nside-1),this.npix=12*this.npface,this.fact2=4/this.npix,this.fact1=(this.nside<<1)*this.fact2,this.order=t.nside2order(this.nside)},t.calculateNSide=function(e){for(var i=0,o=e*e,r=180/Constants.PI,s=5184e4*Constants.PI*r*r,a=Utils.castToInt(s/o),n=a/12,h=Math.sqrt(n),l=t.NS_MAX,c=0,u=0;t.NSIDELIST.length>u;u++)if(l>=Math.abs(h-t.NSIDELIST[u])&&(l=Math.abs(h-t.NSIDELIST[u]),i=t.NSIDELIST[u],c=u),h>i&&t.NS_MAX>h&&(i=t.NSIDELIST[c+1]),h>t.NS_MAX)return console.log("nside cannot be bigger than "+t.NS_MAX),t.NS_MAX;return i},t.nside2order=function(e){"use strict";return(e&e-1)>0?-1:Utils.castToInt(t.log2(e))},t.log2=function(t){"use strict";return Math.log(t)/Math.log(2)},t.prototype.ang2pix_nest=function(e,i){"use strict";var o,r,s,a,n,h,l,c,u,d,p,f,v;if(i>=Constants.TWOPI&&(i-=Constants.TWOPI),0>i&&(i+=Constants.TWOPI),e>Constants.PI||0>e)throw{name:"Illegal argument",message:"theta must be between 0 and "+Constants.PI};if(i>Constants.TWOPI||0>i)throw{name:"Illegal argument",message:"phi must be between 0 and "+Constants.TWOPI};if(r=Math.cos(e),s=Math.abs(r),a=i/Constants.PIOVER2,t.Z0>=s){var g=this.nside*(.5+a),m=.75*this.nside*r,c=g-m,u=g+m;h=c>>this.order,l=u>>this.order,p=h==l?4==h?4:h+4:l>h?h:l+8,f=Utils.castToInt(u&this.nside-1),v=Utils.castToInt(this.nside-(c&this.nside-1)-1)}else{d=Utils.castToInt(a),d>=4&&(d=3),n=a-d;var y=this.nside*Math.sqrt(3*(1-s));c=Utils.castToInt(n*y),u=Utils.castToInt((1-n)*y),c=Math.min(t.NS_MAX-1,c),u=Math.min(t.NS_MAX-1,u),r>=0?(p=d,f=Utils.castToInt(this.nside-u-1),v=Utils.castToInt(this.nside-c-1)):(p=d+8,f=c,v=u)}return o=this.xyf2nest(f,v,p)},t.prototype.xyf2nest=function(t,e,i){"use strict";return(i<<2*this.order)+(this.utab[255&t]|this.utab[255&t>>8]<<16|this.utab[255&t>>16]<<32|this.utab[255&t>>24]<<48|this.utab[255&e]<<1|this.utab[255&e>>8]<<17|this.utab[255&e>>16]<<33|this.utab[255&e>>24]<<49)},t.prototype.nest2xyf=function(t){"use strict";var e={};e.face_num=t>>2*this.order;var i=t&this.npface-1,o=(93823560581120&i)>>16|(614882086624428e4&i)>>31|21845&i|(1431633920&i)>>15;return e.ix=this.ctab[255&o]|this.ctab[255&o>>8]<<4|this.ctab[255&o>>16]<<16|this.ctab[255&o>>24]<<20,i>>=1,o=(93823560581120&i)>>16|(614882086624428e4&i)>>31|21845&i|(1431633920&i)>>15,e.iy=this.ctab[255&o]|this.ctab[255&o>>8]<<4|this.ctab[255&o>>16]<<16|this.ctab[255&o>>24]<<20,e},t.prototype.pix2ang_nest=function(e){"use strict";if(0>e||e>this.npix-1)throw{name:"Illegal argument",message:"ipix out of range"};var i,o,r,s=this.nest2xyf(e),a=s.ix,n=s.iy,h=s.face_num,l=(t.JRLL[h]<<this.order)-a-n-1;this.nside>l?(i=l,o=1-i*i*this.fact2,r=0):l>this.nl3?(i=this.nl4-l,o=i*i*this.fact2-1,r=0):(i=this.nside,o=(this.nl2-l)*this.fact1,r=1&l-this.nside);var c=Math.acos(o),u=(t.JPLL[h]*i+a-n+1+r)/2;u>this.nl4&&(u-=this.nl4),1>u&&(u+=this.nl4);var d=(u-.5*(r+1))*(Constants.PIOVER2/i);return{theta:c,phi:d}},t.nside2Npix=function(e){"use strict";if(0>e||(e&-e)!=e||e>t.NS_MAX)throw{name:"Illegal argument",message:"nside should be >0, power of 2, <"+t.NS_MAX};var i=12*e*e;return i},t.prototype.xyf2ring=function(e,i,o){"use strict";var r,s,a,n=t.JRLL[o]*this.nside-e-i-1;this.nside>n?(r=n,a=2*r*(r-1),s=0):n>3*this.nside?(r=this.nl4-n,a=this.npix-2*(r+1)*r,s=0):(r=this.nside,a=this.ncap+(n-this.nside)*this.nl4,s=1&n-this.nside);var h=(t.JPLL[o]*r+e-i+1+s)/2;return h>this.nl4?h-=this.nl4:1>h&&(h+=this.nl4),a+h-1},t.prototype.nest2ring=function(t){"use strict";var e=this.nest2xyf(t),i=this.xyf2ring(e.ix,e.iy,e.face_num);return i},t.prototype.corners_nest=function(t,e){"use strict";var i=this.nest2ring(t);return this.corners_ring(i,e)},t.prototype.pix2ang_ring=function(t){"use strict";var e,i,o,r,s,a,n,h,l;if(0>t||t>this.npix-1)throw{name:"Illegal argument",message:"ipix out of range"};return a=t+1,this.ncap>=a?(h=a/2,l=Utils.castToInt(h),o=Utils.castToInt(Math.sqrt(h-Math.sqrt(l)))+1,r=a-2*o*(o-1),e=Math.acos(1-o*o*this.fact2),i=(r-.5)*Constants.PI/(2*o)):this.npix-this.ncap>t?(s=t-this.ncap,o=s/this.nl4+this.nside,r=s%this.nl4+1,n=(1&o+this.nside)>0?1:.5,e=Math.acos((this.nl2-o)*this.fact1),i=(r-n)*Constants.PI/this.nl2):(s=this.npix-t,o=Utils.castToInt(.5*(1+Math.sqrt(2*s-1))),r=4*o+1-(s-2*o*(o-1)),e=Math.acos(-1+Math.pow(o,2)*this.fact2),i=(r-.5)*Constants.PI/(2*o)),[e,i]},t.prototype.ring=function(t){"use strict";var e,i,o=0,r=t+1,s=0;return this.ncap>=r?(i=r/2,s=Utils.castToInt(i),o=Utils.castToInt(Math.sqrt(i-Math.sqrt(s)))+1):this.nl2*(5*this.nside+1)>=r?(e=Utils.castToInt(r-this.ncap-1),o=Utils.castToInt(e/this.nl4+this.nside)):(e=this.npix-r+1,i=e/2,s=Utils.castToInt(i),o=Utils.castToInt(Math.sqrt(i-Math.sqrt(s)))+1,o=this.nl4-o),o},t.prototype.integration_limits_in_costh=function(t){"use strict";var e,i,o,r;return r=1*this.nside,this.nside>=t?(i=1-Math.pow(t,2)/3/this.npface,o=1-Math.pow(t-1,2)/3/this.npface,e=t==this.nside?2*(this.nside-1)/3/r:1-Math.pow(t+1,2)/3/this.npface):this.nl3>t?(i=2*(2*this.nside-t)/3/r,o=2*(2*this.nside-t+1)/3/r,e=2*(2*this.nside-t-1)/3/r):(o=t==this.nl3?2*(-this.nside+1)/3/r:-1+Math.pow(4*this.nside-t+1,2)/3/this.npface,e=-1+Math.pow(this.nl4-t-1,2)/3/this.npface,i=-1+Math.pow(this.nl4-t,2)/3/this.npface),[o,i,e]},t.prototype.pixel_boundaries=function(t,e,i,o){var r,s,a,n,h,l,c,u,d=1*this.nside;if(Math.abs(o)>=1-1/3/this.npface)return c=i*Constants.PIOVER2,u=(i+1)*Constants.PIOVER2,[c,u];if(1.5*o>=1)r=Math.sqrt(3*(1-o)),s=1/d/r,a=e,n=a-1,h=t-e,l=h+1,c=Constants.PIOVER2*(Math.max(n*s,1-l*s)+i),u=Constants.PIOVER2*(Math.min(1-h*s,a*s)+i);else if(1.5*o>-1){var p=.5*(1-1.5*o),f=p+1,v=this.nside+t%2;a=e-(v-t)/2,n=a-1,h=(v+t)/2-e,l=h+1,c=Constants.PIOVER2*(Math.max(f-l/d,-p+n/d)+i),u=Constants.PIOVER2*(Math.min(f-h/d,-p+a/d)+i)}else{r=Math.sqrt(3*(1+o)),s=1/d/r;var g=2*this.nside;a=t-g+e,n=a-1,h=g-e,l=h+1,c=Constants.PIOVER2*(Math.max(1-(g-n)*s,(g-l)*s)+i),u=Constants.PIOVER2*(Math.min(1-(g-a)*s,(g-h)*s)+i)}return[c,u]},t.vector=function(t,e){"use strict";var i=1*Math.sin(t)*Math.cos(e),o=1*Math.sin(t)*Math.sin(e),r=1*Math.cos(t);return new SpatialVector(i,o,r)},t.prototype.corners_ring=function(e,i){"use strict";var o=2*i+2,r=Array(o),s=this.pix2ang_ring(e),a=Math.cos(s[0]),n=s[0],h=s[1],l=Utils.castToInt(h/Constants.PIOVER2),c=this.ring(e),u=Math.min(c,Math.min(this.nside,this.nl4-c)),d=0,p=Constants.PIOVER2/u;d=c>=this.nside&&this.nl3>=c?Utils.castToInt(h/p+c%2/2)+1:Utils.castToInt(h/p)+1,d-=l*u;var f=o/2,v=this.integration_limits_in_costh(c),g=Math.acos(v[0]),m=Math.acos(v[2]),y=this.pixel_boundaries(c,d,l,v[0]);if(r[0]=d>u/2?t.vector(g,y[1]):t.vector(g,y[0]),y=this.pixel_boundaries(c,d,l,v[2]),r[f]=d>u/2?t.vector(m,y[1]):t.vector(m,y[0]),1==i){var w=Math.acos(v[1]);y=this.pixel_boundaries(c,d,l,v[1]),r[1]=t.vector(w,y[0]),r[3]=t.vector(w,y[1])}else for(var C=v[2]-v[0],x=C/(i+1),S=1;i>=S;S++)a=v[0]+x*S,n=Math.acos(a),y=this.pixel_boundaries(c,d,l,a),r[S]=t.vector(n,y[0]),r[o-S]=t.vector(n,y[1]);return r},t.vec2Ang=function(t){"use strict";var e=t.z/t.length(),i=Math.acos(e),o=0;return(0!=t.x||0!=t.y)&&(o=Math.atan2(t.y,t.x)),0>o&&(o+=2*Math.PI),[i,o]},t.prototype.queryDisc=function(e,i,o,r){"use strict";if(0>i||i>Constants.PI)throw{name:"Illegal argument",message:"angular radius is in RADIAN and should be in [0,pi]"};var s,a,n,h,l,c,u,d,p,f,v,g,m,y,w,C,x,S,M,b=new LongRangeSetBuilder,T=null,l=i;if(r&&(l+=Constants.PI/this.nl4),T=t.vec2Ang(e),c=T[0],u=T[1],v=this.fact2,g=this.fact1,h=Math.cos(c),M=1/Math.sqrt((1-h)*(1+h)),y=c-l,w=c+l,d=Math.cos(l),x=Math.cos(y),s=this.ringAbove(x)+1,C=Math.cos(w),a=this.ringAbove(C),s>a&&0==a&&(a=s),0>=y)for(var I=1;s>I;++I)this.inRing(I,0,Math.PI,b);for(n=s;a>=n;++n)S=this.nside>n?1-n*n*v:this.nl3>=n?(this.nl2-n)*g:-1+(this.nl4-n)*(this.nl4-n)*v,p=(d-S*h)*M,f=1-S*S-p*p,m=Math.atan2(Math.sqrt(f),p),isNaN(m)&&(m=l),this.inRing(n,u,m,b);if(w>=Math.PI)for(var I=a+1;this.nl4>I;++I)this.inRing(I,0,Math.PI,b,!1);var P;if(o){for(var R=b.items,A=[],O=0;R.length>O;O++){var E=this.ring2nest(R[O]);A.indexOf(E)>=0||A.push(E)}P=A}else P=b.items;return P},t.prototype.inRing=function(t,e,i,o,r){"use strict";var s,a,n,h,l=!1,c=!1,u=1e-12,d=0,p=0,f=0,v=0,g=(e-i)%Constants.TWOPI-u,m=e+i+u,y=(e+i)%Constants.TWOPI+u;if(u>Math.abs(i-Constants.PI)&&(l=!0),t>=this.nside&&this.nl3>=t?(p=t-this.nside+1,n=this.ncap+this.nl4*(p-1),h=n+this.nl4-1,s=p%2,a=this.nl4):(this.nside>t?(p=t,n=2*p*(p-1),h=n+4*p-1):(p=4*this.nside-t,n=this.npix-2*p*(p+1),h=n+4*p-1),a=4*p,s=1),l)return o.appendRange(n,h),void 0;if(d=s/2,r)f=Math.round(a*g/Constants.TWOPI-d),v=Math.round(a*m/Constants.TWOPI-d),f%=a,v>a&&(v%=a);else{if(f=Math.ceil(a*g/Constants.TWOPI-d),v=Utils.castToInt(a*y/Constants.TWOPI-d),f>v&&1==t&&(v=Utils.castToInt(a*m/Constants.TWOPI-d)),f==v+1&&(f=v),1==f-v&&Constants.PI>i*a)return console.log("the interval is too small and avay from center"),void 0;f=Math.min(f,a-1),v=Math.max(v,0)}if(f>v&&(c=!0),c)f+=n,v+=n,o.appendRange(n,v),o.appendRange(f,h);else{if(0>f)return f=Math.abs(f),o.appendRange(n,n+v),o.appendRange(h-f+1,h),void 0;f+=n,v+=n,o.appendRange(f,v)}},t.prototype.ringAbove=function(t){"use strict";var e=Math.abs(t);if(e>Constants.TWOTHIRD){var i=Utils.castToInt(this.nside*Math.sqrt(3*(1-e)));return t>0?i:4*this.nside-i-1}return Utils.castToInt(this.nside*(2-1.5*t))},t.prototype.ring2nest=function(t){"use strict";var e=this.ring2xyf(t);return this.xyf2nest(e.ix,e.iy,e.face_num)},t.prototype.ring2xyf=function(e){"use strict";var i,o,r,s,a={};if(this.ncap>e){i=Utils.castToInt(.5*(1+Math.sqrt(1+2*e))),o=e+1-2*i*(i-1),r=0,s=i,a.face_num=0;var n=o-1;n>=2*i&&(a.face_num=2,n-=2*i),n>=i&&++a.face_num}else if(this.npix-this.ncap>e){var h=e-this.ncap;this.order>=0?(i=(h>>this.order+2)+this.nside,o=(h&this.nl4-1)+1):(i=h/this.nl4+this.nside,o=h%this.nl4+1),r=1&i+this.nside,s=this.nside;var l,c,u=i-this.nside+1,d=this.nl2+2-u;this.order>=0?(l=o-Utils.castToInt(u/2)+this.nside-1>>this.order,c=o-Utils.castToInt(d/2)+this.nside-1>>this.order):(l=(o-Utils.castToInt(u/2)+this.nside-1)/this.nside,c=(o-Utils.castToInt(d/2)+this.nside-1)/this.nside),a.face_num=c==l?4==c?4:Utils.castToInt(c)+4:l>c?Utils.castToInt(c):Utils.castToInt(l)+8}else{var h=this.npix-e;i=Utils.castToInt(.5*(1+Math.sqrt(2*h-1))),o=4*i+1-(h-2*i*(i-1)),r=0,s=i,i=2*this.nl2-i,a.face_num=8;var n=o-1;n>=2*s&&(a.face_num=10,n-=2*s),n>=s&&++a.face_num}var p=i-t.JRLL[a.face_num]*this.nside+1,f=2*o-t.JPLL[a.face_num]*s-r-1;return f>=this.nl2&&(f-=8*this.nside),a.ix=f-p>>1,a.iy=-(f+p)>>1,a},t}(),Utils=function(){},Utils.radecToPolar=function(t,e){return{theta:Math.PI/2-e/180*Math.PI,phi:t/180*Math.PI}},Utils.polarToRadec=function(t,e){return{ra:180*e/Math.PI,dec:180*(Math.PI/2-t)/Math.PI}},Utils.castToInt=function(t){return t>0?Math.floor(t):Math.ceil(t)},AstroMath.D2R=Math.PI/180,AstroMath.R2D=180/Math.PI,AstroMath.sign=function(t){return t>0?1:0>t?-1:0},AstroMath.cosd=function(t){if(0==t%90){var e=Math.abs(Math.floor(t/90+.5))%4;switch(e){case 0:return 1;case 1:return 0;case 2:return-1;case 3:return 0}}return Math.cos(t*AstroMath.D2R)},AstroMath.sind=function(t){if(0===t%90){var e=Math.abs(Math.floor(t/90-.5))%4;switch(e){case 0:return 1;case 1:return 0;case 2:return-1;case 3:return 0}}return Math.sin(t*AstroMath.D2R)},AstroMath.tand=function(t){var e;return e=t%360,0==e||180==Math.abs(e)?0:45==e||225==e?1:-135==e||-315==e?-1:Math.tan(t*AstroMath.D2R)},AstroMath.asind=function(t){return Math.asin(t)*AstroMath.R2D},AstroMath.acosd=function(t){return Math.acos(t)*AstroMath.R2D},AstroMath.atand=function(t){return Math.atan(t)*AstroMath.R2D},AstroMath.atan2=function(t,e){if(0==t)return e>0?0:0>e?Math.PI:0/0;var i=AstroMath.sign(t);if(0==e)return Math.PI/2*i;var o=Math.atan(Math.abs(t/e));return e>0?o*i:0>e?(Math.PI-o)*i:void 0},AstroMath.atan2d=function(t,e){return AstroMath.atan2(t,e)*AstroMath.R2D},AstroMath.cosh=function(t){return(Math.exp(t)+Math.exp(-t))/2},AstroMath.sinh=function(t){return(Math.exp(t)-Math.exp(-t))/2},AstroMath.tanh=function(t){return(Math.exp(t)-Math.exp(-t))/(Math.exp(t)+Math.exp(-t))},AstroMath.acosh=function(t){return Math.log(t+Math.sqrt(t*t-1))},AstroMath.asinh=function(t){return Math.log(t+Math.sqrt(t*t+1))},AstroMath.atanh=function(t){return.5*Math.log((1+t)/(1-t))},AstroMath.sinc=function(t){var e,i=Math.abs(t);return.001>=i?(i*=i,e=1-i*(1-i/20)/6):e=Math.sin(i)/i,e},AstroMath.asinc=function(t){var e,i=Math.abs(t);return.001>=i?(i*=i,e=1+i*(6+.45*i)/6):e=Math.asin(i)/i,e},AstroMath.hypot=function(t,e){return Math.sqrt(t*t+e*e)},AstroMath.eulerMatrix=function(t,e,i){var o=Array(3);o[0]=Array(3),o[1]=Array(3),o[2]=Array(3);var r=AstroMath.cosd(t),s=AstroMath.sind(t),a=AstroMath.cosd(e),n=AstroMath.sind(e),h=AstroMath.cosd(i),l=AstroMath.sind(i);return o[0][0]=h*a*r-l*s,o[0][1]=-l*a*r-h*s,o[0][2]=-n*r,o[1][0]=h*a*s+l*r,o[1][1]=-l*a*s+h*r,o[1][2]=-n*s,o[2][0]=-n*h,o[2][1]=-n*r,o[2][2]=a,o},AstroMath.displayMatrix=function(t){for(var e=t.length,i=0,o=0;e>o;o++)t[o].length>i&&(i=t[o].length);for(var r="<table>\n",o=0;e>o;o++){r+="<tr>";for(var s=0;e>s;s++)r+="<td>",t[o].length>o&&(r+=""+t[o][s]),r+="</td>";r+="</td>\n"}return r+="</table>\n"},Projection.PROJ_TAN=1,Projection.PROJ_TAN2=2,Projection.PROJ_STG=2,Projection.PROJ_SIN=3,Projection.PROJ_SIN2=4,Projection.PROJ_ZEA=4,Projection.PROJ_ARC=5,Projection.PROJ_SCHMIDT=5,Projection.PROJ_AITOFF=6,Projection.PROJ_AIT=6,Projection.PROJ_GLS=7,Projection.PROJ_MERCATOR=8,Projection.PROJ_MER=8,Projection.PROJ_LAM=9,Projection.PROJ_LAMBERT=9,Projection.PROJ_TSC=10,Projection.PROJ_QSC=11,Projection.PROJ_LIST=["Mercator",Projection.PROJ_MERCATOR,"Gnomonic",Projection.PROJ_TAN,"Stereographic",Projection.PROJ_TAN2,"Orthographic",Projection.PROJ_SIN,"Zenithal",Projection.PROJ_ZEA,"Schmidt",Projection.PROJ_SCHMIDT,"Aitoff",Projection.PROJ_AITOFF,"Lambert",Projection.PROJ_LAMBERT],Projection.PROJ_NAME=["-","Gnomonic","Stereographic","Orthographic","Equal-area","Schmidt plates","Aitoff","Global sin","Mercator","Lambert"],Projection.prototype={setCenter:function(t,e){this.ROT=this.tr_oR(t,e)},setProjection:function(t){this.PROJECTION=t},project:function(t,e){var i=this.tr_ou(t,e),o=this.tr_uu(i,this.ROT),r=this.tr_up(this.PROJECTION,o);return null==r?null:{X:-r[0],Y:-r[1]}},unproject:function(t,e){t=-t,e=-e;var i=this.tr_pu(this.PROJECTION,t,e),o=this.tr_uu1(i,this.ROT),r=this.tr_uo(o);return{ra:r[0],dec:r[1]}},tr_up:function(t,e){var i,o,r,s,a,n=e[0],h=e[1],l=e[2];if(i=AstroMath.hypot(n,h),0==i&&0==l)return null;switch(t){default:r=null;break;case Projection.PROJ_AITOFF:o=Math.sqrt(i*(i+n)/2),s=Math.sqrt(2*i*(i-n)),o=Math.sqrt((1+o)/2),s/=o,a=l/o,0>h&&(s=-s),r=[s,a];break;case Projection.PROJ_GLS:a=Math.asin(l),s=0!=i?Math.atan2(h,n)*i:0,r=[s,a];break;case Projection.PROJ_MERCATOR:0!=i?(s=Math.atan2(h,n),a=AstroMath.atanh(l),r=[s,a]):r=null;break;case Projection.PROJ_TAN:n>0?(s=h/n,a=l/n,r=[s,a]):r=null;break;case Projection.PROJ_TAN2:o=(1+n)/2,o>0?(s=h/o,a=l/o,r=[s,a]):r=null;break;case Projection.PROJ_ARC:-1>=n?(s=Math.PI,a=0):(i=AstroMath.hypot(h,l),o=n>0?AstroMath.asinc(i):Math.acos(n)/i,s=h*o,a=l*o),r=[s,a];break;case Projection.PROJ_SIN:n>=0?(s=h,a=l,r=[s,a]):r=null;break;case Projection.PROJ_SIN2:o=Math.sqrt((1+n)/2),0!=o?(s=h/o,a=l/o):(s=2,a=0),r=[s,a];break;case Projection.PROJ_LAMBERT:a=l,s=0,0!=i&&(s=Math.atan2(h,n)),r=[s,a]}return r},tr_pu:function(t,e,i){var o,r,s,a,n;switch(t){default:return null;case Projection.PROJ_AITOFF:if(o=e*e/8+i*i/2,o>1)return null;s=1-o,r=Math.sqrt(1-o/2),a=e*r/2,n=i*r,o=AstroMath.hypot(s,a),0!=o&&(r=s,s=(r*r-a*a)/o,a=2*r*a/o);break;case Projection.PROJ_GLS:if(n=Math.sin(i),o=1-n*n,0>o)return null;o=Math.sqrt(o),r=0!=o?e/o:0,s=o*Math.cos(r),a=o*Math.sin(r);break;case Projection.PROJ_MERCATOR:n=AstroMath.tanh(i),o=1/AstroMath.cosh(i),s=o*Math.cos(e),a=o*Math.sin(e);break;case Projection.PROJ_LAMBERT:if(n=i,o=1-n*n,0>o)return null;o=Math.sqrt(o),s=o*Math.cos(e),a=o*Math.sin(e);break;case Projection.PROJ_TAN:s=1/Math.sqrt(1+e*e+i*i),a=e*s,n=i*s;break;case Projection.PROJ_TAN2:o=(e*e+i*i)/4,r=1+o,s=(1-o)/r,a=e/r,n=i/r;break;case Projection.PROJ_ARC:if(o=AstroMath.hypot(e,i),o>Math.PI)return null;r=AstroMath.sinc(o),s=Math.cos(o),a=r*e,n=r*i;break;case Projection.PROJ_SIN:if(r=1-e*e-i*i,0>r)return null;s=Math.sqrt(r),a=e,n=i;break;case Projection.PROJ_SIN2:if(o=(e*e+i*i)/4,o>1)return null;r=Math.sqrt(1-o),s=1-2*o,a=r*e,n=r*i}return[s,a,n]},tr_oR:function(t,e){var i=Array(3);return i[0]=Array(3),i[1]=Array(3),i[2]=Array(3),i[2][2]=AstroMath.cosd(e),i[0][2]=AstroMath.sind(e),i[1][1]=AstroMath.cosd(t),i[1][0]=-AstroMath.sind(t),i[1][2]=0,i[0][0]=i[2][2]*i[1][1],i[0][1]=-i[2][2]*i[1][0],i[2][0]=-i[0][2]*i[1][1],i[2][1]=i[0][2]*i[1][0],i},tr_ou:function(t,e){var i=Array(3),o=AstroMath.cosd(e);return i[0]=o*AstroMath.cosd(t),i[1]=o*AstroMath.sind(t),i[2]=AstroMath.sind(e),i},tr_uu:function(t,e){var i=Array(3),o=t[0],r=t[1],s=t[2];return i[0]=e[0][0]*o+e[0][1]*r+e[0][2]*s,i[1]=e[1][0]*o+e[1][1]*r+e[1][2]*s,i[2]=e[2][0]*o+e[2][1]*r+e[2][2]*s,i},tr_uu1:function(t,e){var i=Array(3),o=t[0],r=t[1],s=t[2];return i[0]=e[0][0]*o+e[1][0]*r+e[2][0]*s,i[1]=e[0][1]*o+e[1][1]*r+e[2][1]*s,i[2]=e[0][2]*o+e[1][2]*r+e[2][2]*s,i},tr_uo:function(t){var e,i,o=t[0],r=t[1],s=t[2],a=o*o+r*r;if(0==a){if(0==s)return null;e=0,i=s>0?90:-90}else i=AstroMath.atand(s/Math.sqrt(a)),e=AstroMath.atan2d(r,o),0>e&&(e+=360);return[e,i]}},Coo.factor=[3600,60,1],Coo.prototype={setFrame:function(t){this.frame=t},computeDirCos:function(){var t=AstroMath.cosd(this.lat);this.x=t*AstroMath.cosd(this.lon),this.y=t*AstroMath.sind(this.lon),this.z=AstroMath.sind(this.lat)},computeLonLat:function(){var t=this.x*this.x+this.y*this.y;
this.lon=0,0==t?0==this.z?(this.lon=0/0,this.lat=0/0):this.lat=this.z>0?90:-90:(this.lon=AstroMath.atan2d(this.y,this.x),this.lat=AstroMath.atan2d(this.z,Math.sqrt(t)),0>this.lon&&(this.lon+=360))},dist2:function(t){var e=t.x-this.x,i=e*e;return e=t.y-this.y,i+=e*e,e=t.z-this.z,i+=e*e},distance:function(t){return 0==t.x&&0==t.y&&0==t.z?0/0:0==this.x&&0==this.y&&0==this.z?0/0:2*AstroMath.asind(.5*Math.sqrt(this.dist2(t)))},convertTo:function(t){this.frame.equals(t)||(this.frame.toICRS(this.coo),t.fromICRS(this.coo),this.frame=t,this.lon=this.lat=0/0)},rotate:function(t){var e,i,o;t!=Umatrix3&&(e=t[0][0]*this.x+t[0][1]*this.y+t[0][2]*this.z,i=t[1][0]*this.x+t[1][1]*this.y+t[1][2]*this.z,o=t[2][0]*this.x+t[2][1]*this.y+t[2][2]*this.z,this.x=e,this.y=i,this.z=o,this.lon=this.lat=0/0)},rotate_1:function(t){var e,i,o;t!=Umatrix3&&(e=t[0][0]*this.x+t[1][0]*this.y+t[2][0]*this.z,i=t[0][1]*this.x+t[1][1]*this.y+t[2][1]*this.z,o=t[0][2]*this.x+t[1][2]*this.y+t[2][2]*this.z,this.x=e,this.y=i,this.z=o,this.lon=this.lat=0/0)},equals:function(t){return this.x==t.x&&this.y==t.y&&this.z==t.z},parse:function(t){var e=t.indexOf("+");if(0>e&&(e=t.indexOf("-")),0>e&&(e=t.indexOf(" ")),0>e)return this.lon=0/0,this.lat=0/0,this.prec=0,!1;var i=t.substring(0,e),o=t.substring(e);return this.lon=this.parseLon(i),this.lat=this.parseLat(o),!0},parseLon:function(t){var t=t.trim();if(0>t.indexOf(" ")){var e=t.indexOf(".");return this.prec=0>e?0:t.length-e-1,parseFloat(t)}for(var i=new Tokenizer(t," "),o=0,r=0,s=0;i.hasMore();){var a=i.nextToken(),n=a.indexOf(".");switch(r+=parseFloat(a)*Coo.factor[o],o){case 0:s=0>n?1:2;break;case 1:s=0>n?3:4;break;case 2:s=0>n?5:4+a.length-n;default:}o++}return this.prec=s,15*r/3600},parseLat:function(t){var e,t=t.trim();if("-"==t.charAt(0)?(e=-1,t=t.substring(1)):"-"==t.charAt(0)?(e=1,t=t.substring(1)):e=1,0>t.indexOf(" ")){var i=t.indexOf(".");return this.prec=0>i?0:t.length-i-1,parseFloat(t)*e}for(var o=new Tokenizer(t," "),r=0,s=0,a=0;o.hasMore();){var n=o.nextToken(),h=n.indexOf(".");switch(s+=parseFloat(n)*Coo.factor[r],r){case 0:a=0>h?1:2;break;case 1:a=0>h?3:4;break;case 2:a=0>h?5:4+n.length-h;default:}r++}return this.prec=a,s*e/3600},format:function(t){isNaN(this.lon)&&this.computeLonLat();var e="",i="";if(t.indexOf("d")>=0)e=Numbers.format(this.lon,this.prec),i=Numbers.format(this.lat,this.prec);else var o=this.lon/15,e=Numbers.toSexagesimal(o,this.prec+1,!1),i=Numbers.toSexagesimal(this.lat,this.prec,!1);return this.lat>0&&(i="+"+i),t.indexOf("/")>=0?e+" "+i:t.indexOf("2")>=0?[e,i]:e+i}},Tokenizer.prototype={hasMore:function(){return this.pos<this.string.length},nextToken:function(){for(var t=this.pos;this.string.length>t&&this.string.charAt(t)==this.sep;)t++;for(var e=t;this.string.length>e&&this.string.charAt(e)!=this.sep;)e++;return this.pos=e,this.string.substring(t,e)}},Strings.trim=function(t,e){for(var i=0,o=t.length-1;t.length>i&&t.charAt(i)==e;)i++;if(i==t.length)return"";for(;o>i&&t.charAt(o)==e;)o--;return t.substring(i,o+1)},Numbers.pow10=[1,10,100,1e3,1e4,1e5,1e6,1e7,1e8,1e9,1e10,1e11,1e12,1e13,1e14],Numbers.rndval=[.5,.05,.005,5e-4,5e-5,5e-6,5e-7,5e-8,5e-9,5e-10,5e-11,5e-12,5e-13,5e-14,5e-14],Numbers.format=function(t,e){if(0>=e)return""+Math.round(t);var i=""+t,o=i.indexOf("."),r=o>=0?i.length-o-1:0;if(e>=r){0>o&&(i+=".");for(var s=0;e-r>s;s++)i+="0";return i}return i=""+(t+Numbers.rndval[e]),i.substr(0,o+e+1)},Numbers.toSexagesimal=function(t,e,i){var o=0>t?"-":i?"+":"",r=Math.abs(t);switch(e){case 1:var s=Math.round(r);return o+(""+s);case 2:return o+Numbers.format(r,1);case 3:var s=Math.floor(r),a=Math.round(60*(r-s));return o+s+" "+a;case 4:var s=Math.floor(r),a=60*(r-s);return o+s+" "+Numbers.format(a,1);case 5:var s=Math.floor(r),a=60*(r-s),n=Math.floor(a),h=Math.round(60*(a-n));return o+s+" "+n+" "+h;case 6:case 7:case 8:var s=Math.floor(r);10>s&&(s="0"+s);var a=60*(r-s),n=Math.floor(a);10>n&&(n="0"+n);var h=60*(a-n);return o+s+" "+n+" "+Numbers.format(h,e-5);default:return o+Numbers.format(r,1)}},CooConversion=function(){var t={};return t.GALACTIC_TO_J2000=[-.0548755604024359,.4941094279435681,-.867666148981161,-.8734370902479237,-.4448296299195045,-.1980763734646737,-.4838350155267381,.7469822444763707,.4559837762325372],t.J2000_TO_GALACTIC=[-.0548755604024359,-.873437090247923,-.4838350155267381,.4941094279435681,-.4448296299195045,.7469822444763707,-.867666148981161,-.1980763734646737,.4559837762325372],t.Transform=function(t,e){t[0]=t[0]*Math.PI/180,t[1]=t[1]*Math.PI/180;var i=[Math.cos(t[0])*Math.cos(t[1]),Math.sin(t[0])*Math.cos(t[1]),Math.sin(t[1])],o=[i[0]*e[0]+i[1]*e[1]+i[2]*e[2],i[0]*e[3]+i[1]*e[4]+i[2]*e[5],i[0]*e[6]+i[1]*e[7]+i[2]*e[8]],r=Math.sqrt(o[0]*o[0]+o[1]*o[1]+o[2]*o[2]),s=[0,0];s[1]=Math.asin(o[2]/r);var a=o[0]/r/Math.cos(s[1]),n=o[1]/r/Math.cos(s[1]);return s[0]=Math.atan2(n,a),0>s[0]&&(s[0]=s[0]+2*Math.PI),s[0]=180*s[0]/Math.PI,s[1]=180*s[1]/Math.PI,s},t.GalacticToJ2000=function(e){return t.Transform(e,t.GALACTIC_TO_J2000)},t.J2000ToGalactic=function(e){return t.Transform(e,t.J2000_TO_GALACTIC)},t}(),Sesame=function(){return Sesame={},Sesame.cache={},Sesame.resolve=function(t,e,i){var o="http://cds.u-strasbg.fr/cgi-bin/nph-sesame.jsonp?";$.ajax({url:o,data:{object:t},method:"GET",dataType:"jsonp",success:function(t){t.Target&&t.Target.Resolver&&t.Target.Resolver?e(t):i(t)},error:i})},Sesame}(),HealpixCache=function(){var t={};return t.staticCache={corners:{nside8:[]}},t.dynamicCache={},t.lastNside=8,t.hpxIdxCache=null,t.init=function(){var e=new HealpixIndex(8);e.init();for(var i=HealpixIndex.nside2Npix(8),o=0;i>o;o++)t.staticCache.corners.nside8[o]=e.corners_nest(o,1);t.hpxIdxCache=e},t.corners_nest=function(e,i){return 8==i?t.staticCache.corners.nside8[e]:(i!=t.lastNside&&(t.hpxIdxCache=new HealpixIndex(i),t.hpxIdxCache.init(),t.lastNside=i),t.hpxIdxCache.corners_nest(e,1))},t}(),Utils=Utils||{},Utils.cssScale=void 0,HTMLCanvasElement.prototype.relMouseCoords=relMouseCoords,Function.prototype.bind||(Function.prototype.bind=function(t){if("function"!=typeof this)throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var e=[].slice,i=e.call(arguments,1),o=this,r=function(){},s=function(){return o.apply(this instanceof r?this:t||{},i.concat(e.call(arguments)))};return s.prototype=this.prototype,s}),$=$||jQuery,$.urlParam=function(t,e){return void 0===e&&(e=location.search),decodeURIComponent((RegExp("[?|&]"+t+"="+"([^&;]+?)(&|#|;|$)").exec(e)||[,""])[1].replace(/\+/g,"%20"))||null},Utils.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},Utils.debounce=function(t,e){var i=null;return function(){var o=this,r=arguments;clearTimeout(i),i=setTimeout(function(){t.apply(o,r)},e)}},Utils.LRUCache=function(t){this._keys=[],this._items={},this._expires={},this._size=0,this._maxsize=t||1024},Utils.LRUCache.prototype={set:function(t,e){var i=this._keys,o=this._items,r=this._expires,s=this._size,a=this._maxsize;s>=a&&(i.sort(function(t,e){return r[t]>r[e]?-1:r[t]<r[e]?1:0}),s--,delete r[i[s]],delete o[i[s]]),i[s]=t,o[t]=e,r[t]=Date.now(),s++,this._keys=i,this._items=o,this._expires=r,this._size=s},get:function(t){var e=this._items[t];return e&&(this._expires[t]=Date.now()),e},keys:function(){return this._keys}},Color={},Color.curIdx=0,Color.colors=["#ff0000","#0000ff","#99cc00","#ffff00","#000066","#00ffff","#9900cc","#0099cc","#cc9900","#cc0099","#00cc99","#663333","#ffcc9a","#ff9acc","#ccff33","#660000","#ffcc33","#ff00ff","#00ff00","#ffffff"],Color.getNextColor=function(){var t=Color.colors[Color.curIdx%Color.colors.length];return Color.curIdx++,t},AladinUtils=function(){return{xyToView:function(t,e,i,o,r,s){return{vx:AladinUtils.myRound(r/2*(1+s*t)-(r-i)/2),vy:AladinUtils.myRound(r/2*(1+s*e)-(r-o)/2)}},viewToXy:function(t,e,i,o,r,s){return{x:((2*t+(r-i))/r-1)/s,y:((2*e+(r-o))/r-1)/s}},myRound:function(t){return 0>t?-1*(0|-t):0|t},isHpxPixVisible:function(t,e,i){for(var o=0;t.length>o;o++)if(t[o].vx>=-20&&e+20>t[o].vx&&t[o].vy>=-20&&i+20>t[o].vy)return!0;return!1},ipixToIpix:function(t,e,i){},getZoomFactorForAngle:function(t,e){var i={ra:0,dec:0},o={ra:t,dec:0},r=new Projection(t/2,0);r.setProjection(e);var s=r.project(i.ra,i.dec),a=r.project(o.ra,o.dec),n=1/(s.X-a.Y);return n}}}(),ProjectionEnum={SIN:Projection.PROJ_SIN,AITOFF:Projection.PROJ_AITOFF},CooFrameEnum=function(){return{J2000:"J2000",GAL:"Galactic"}}(),CooFrameEnum.fromString=function(t,e){return t?(t=t.toLowerCase().replace(/^\s+|\s+$/g,""),0==t.indexOf("j2000")||0==t.indexOf("icrs")?CooFrameEnum.J2000:0==t.indexOf("gal")?CooFrameEnum.GAL:e?e:null):e?e:null},Downloader=function(){var t=4,e=!1,i=700,o=function(t){this.view=t,this.nbDownloads=0,this.dlQueue=[],this.urlsInQueue={}};return o.prototype.requestDownload=function(t,e,i){e in this.urlsInQueue||(this.dlQueue.push({img:t,url:e,cors:i}),this.urlsInQueue[e]=1,this.tryDownload())},o.prototype.tryDownload=function(){for(;this.dlQueue.length>0&&t>this.nbDownloads;)this.startDownloadNext()},o.prototype.startDownloadNext=function(){var t=this.dlQueue.shift();if(t){this.nbDownloads++;var e=this;t.img.onload=function(){e.completeDownload(this,!0)},t.img.onerror=function(){e.completeDownload(this,!1)},t.cors?t.img.crossOrigin="anonymous":void 0!==t.img.crossOrigin&&delete t.img.crossOrigin,t.img.src=t.url}},o.prototype.completeDownload=function(t,o){if(delete this.urlsInQueue[t.src],t.onerror=null,t.onload=null,this.nbDownloads--,o){if(e){var r=(new Date).getTime();t.fadingStart=r,t.fadingEnd=r+i}this.view.requestRedraw()}else t.dlError=!0;this.tryDownload()},o}(),Footprint=function(){return Footprint=function(t){this.polygons=t,this.overlay=null,this.isShowing=!0,this.isSelected=!1},Footprint.prototype.setOverlay=function(t){this.overlay=t},Footprint.prototype.show=function(){this.isShowing||(this.isShowing=!0,this.overlay&&this.overlay.reportChange())},Footprint.prototype.hide=function(){this.isShowing&&(this.isShowing=!1,this.overlay&&this.overlay.reportChange())},Footprint.prototype.select=function(){this.isSelected||(this.isSelected=!0,this.overlay&&this.overlay.reportChange())},Footprint.prototype.deselect=function(){this.isSelected&&(this.isSelected=!1,this.overlay&&this.overlay.reportChange())},Footprint}(),Popup=function(){return Popup=function(t){this.domEl=$('<div class="aladin-popup-container"><div class="aladin-popup"><a class="aladin-closeBtn">&times;</a><div class="aladin-popupTitle"></div><div class="aladin-popupText"></div></div><div class="aladin-popup-arrow"></div></div>'),this.domEl.appendTo(t);var e=this;this.domEl.find(".aladin-closeBtn").click(function(){e.hide()})},Popup.prototype.hide=function(){this.domEl.hide()},Popup.prototype.show=function(){this.domEl.show()},Popup.prototype.setTitle=function(t){this.domEl.find(".aladin-popupTitle").html(t)},Popup.prototype.setText=function(t){this.domEl.find(".aladin-popupText").html(t),this.w=this.domEl.outerWidth(),this.h=this.domEl.outerHeight()},Popup.prototype.setSource=function(t){this.source&&(this.source.popup=null),t.popup=this,this.source=t,this.setPosition(t.x,t.y)},Popup.prototype.setPosition=function(t,e){var i=t-this.w/2,o=e-this.h+this.source.catalog.sourceSize/2;this.domEl[0].style.left=i+"px",this.domEl[0].style.top=o+"px"},Popup}(),Overlay=function(){return Overlay=function(t){t=t||{},this.name=t.name||"overlay",this.color=t.color||Color.getNextColor(),this.overlays=[],this.isShowing=!0},Overlay.parseSTCS=function(t){for(var e,i=[],o=t.match(/\S+/g),r=0,s=o.length;s>r;){var a=o[r].toLowerCase();if("polygon"==a&&(e=[],r++,frame=o[r].toLowerCase(),"icrs"==frame||"j2000"==frame)){for(;s>r+2;){var n=parseFloat(o[r+1]);if(isNaN(n))break;var h=parseFloat(o[r+2]);e.push([n,h]),r+=2}e.push(e[0]),i.push(e)}r++}return i},Overlay.prototype.addFootprints=function(t){this.overlays=this.overlays.concat(t);for(var e=0,i=t.length;i>e;e++)t[e].setOverlay(this);this.view.requestRedraw()},Overlay.prototype.getFootprint=function(t){return this.footprints.length>t?this.footprints[t]:null},Overlay.prototype.setView=function(t){this.view=t},Overlay.prototype.removeAll=function(){this.overlays=[]},Overlay.prototype.draw=function(t,e,i,o,r,s,a){t.strokeStyle=this.color,t.lineWidth=2,t.beginPath(),xyviews=[];for(var n=0,h=this.overlays.length;h>n;n++)xyviews.push(this.drawFootprint(this.overlays[n],t,e,i,o,r,s,a));t.stroke(),t.strokeStyle=Overlay.increase_brightness(this.color,80),t.beginPath();for(var n=0,h=this.overlays.length;h>n;n++)this.overlays[n].isSelected&&this.drawFootprintSelected(t,xyviews[n]);t.stroke()},Overlay.increase_brightness=function(t,e){t=t.replace(/^\s*#|\s*$/g,""),3==t.length&&(t=t.replace(/(.)/g,"$1$1"));var i=parseInt(t.substr(0,2),16),o=parseInt(t.substr(2,2),16),r=parseInt(t.substr(4,2),16);return"#"+(0|256+i+(256-i)*e/100).toString(16).substr(1)+(0|256+o+(256-o)*e/100).toString(16).substr(1)+(0|256+r+(256-r)*e/100).toString(16).substr(1)},Overlay.prototype.drawFootprint=function(t,e,i,o,r,s,a,n){if(!t.isShowing)return null;for(var h=[],l=!1,c=t.polygons,u=0,d=c.length;d>u;u++){var p;if(o!=CooFrameEnum.J2000){var f=CooConversion.J2000ToGalactic([c[u][0],c[u][1]]);p=i.project(f[0],f[1])}else p=i.project(c[u][0],c[u][1]);if(!p)return null;var v=AladinUtils.xyToView(p.X,p.Y,r,s,a,n);h.push(v),!l&&r>v.vx&&v.vx>=0&&s>=v.vy&&v.vy>=0&&(l=!0)}if(l){e.moveTo(h[0].vx,h[0].vy);for(var u=1,d=h.length;d>u;u++)e.lineTo(h[u].vx,h[u].vy)}return h},Overlay.prototype.drawFootprintSelected=function(t,e){if(e){var i=e;t.moveTo(i[0].vx,i[0].vy);for(var o=1,r=i.length;r>o;o++)t.lineTo(i[o].vx,i[o].vy)}},Overlay.prototype.reportChange=function(){this.view.requestRedraw()},Overlay}(),cds.Source=function(){return cds.Source=function(t,e,i,o){this.ra=t,this.dec=e,this.data=i,this.catalog=null,this.marker=o&&o.marker||!1,this.marker&&(this.popupTitle=o&&o.popupTitle?o.popupTitle:"",this.popupDesc=o&&o.popupDesc?o.popupDesc:""),this.isShowing=!0,this.isSelected=!1},cds.Source.prototype.setCatalog=function(t){this.catalog=t},cds.Source.prototype.show=function(){this.isShowing||(this.isShowing=!0,this.catalog&&this.catalog.reportChange())},cds.Source.prototype.hide=function(){this.isShowing&&(this.isShowing=!1,this.catalog&&this.catalog.reportChange())},cds.Source.prototype.select=function(){this.isSelected||(this.isSelected=!0,this.catalog&&this.catalog.reportChange())},cds.Source.prototype.deselect=function(){this.isSelected&&(this.isSelected=!1,this.catalog&&this.catalog.reportChange())},cds.Source}(),ProgressiveCat=function(){function t(t,e){var i=["name","ID","ucd","utype","unit","datatype","arraysize","width","precision"],o=[],r=0;return t.keyRa=t.keyDec=null,$(e).find("FIELD").each(function(){for(var e={},s=0;i.length>s;s++){var a=i[s];$(this).attr(a)&&(e[a]=$(this).attr(a))}e.ID||(e.ID="col_"+r),t.keyRa||!e.ucd||0!=e.ucd.indexOf("pos.eq.ra")&&0!=e.ucd.indexOf("POS_EQ_RA")||(t.keyRa=e.name?e.name:e.ID),t.keyDec||!e.ucd||0!=e.ucd.indexOf("pos.eq.dec")&&0!=e.ucd.indexOf("POS_EQ_DEC")||(t.keyDec=e.name?e.name:e.ID),o.push(e),r++}),o}function e(t,e,i){if(!t.keyRa||!t.keyDec)return[];lines=e.split("\n");for(var o=[],r=0;i.length>r;r++)i[r].name?o.push(i[r].name):o.push(i[r].ID);for(var s=[],a=new Coo,n=2;lines.length>n;n++){var h={},l=lines[n].split("	");if(!(l.length<o.length)){for(var c=0;o.length>c;c++)h[o[c]]=l[c];var u,d;Utils.isNumber(h[t.keyRa])&&Utils.isNumber(h[t.keyDec])?(u=parseFloat(h[t.keyRa]),d=parseFloat(h[t.keyDec])):(a.parse(h[t.keyRa]+" "+h[t.keyDec]),u=a.lon,d=a.lat),s.push(new cds.Source(u,d,h))}}return s}return ProgressiveCat=function(t,e,i,o){o=o||{},this.type="progressivecat",this.rootUrl=t,this.frame=CooFrameEnum.fromString(e)||CooFrameEnum.J2000,this.maxOrder=i,this.isShowing=!0,this.name=o.name||"progressive-cat",this.color=o.color||Color.getNextColor(),this.sourceSize=o.sourceSize||10,this.sourcesCache=new Utils.LRUCache(100),this.cacheCanvas=document.createElement("canvas"),this.cacheCanvas.width=this.sourceSize,this.cacheCanvas.height=this.sourceSize;var r=this.cacheCanvas.getContext("2d");r.beginPath(),r.strokeStyle=this.color,r.lineWidth=2,r.moveTo(0,0),r.lineTo(0,this.sourceSize),r.lineTo(this.sourceSize,this.sourceSize),r.lineTo(this.sourceSize,0),r.lineTo(0,0),r.stroke()},ProgressiveCat.prototype={init:function(t){this.view=t,this.level3Sources||this.loadLevel2Sources()},loadLevel2Sources:function(){var i=this;$.ajax({url:i.rootUrl+"/"+"Norder2/Allsky.xml",method:"GET",success:function(o){i.fields=t(i,o),i.level2Sources=e(i,$(o).find("CSV").text(),i.fields),i.loadLevel3Sources()},error:function(t){console.log("Something went wrong: "+t)}})},loadLevel3Sources:function(){var t=this;$.ajax({url:t.rootUrl+"/"+"Norder3/Allsky.xml",method:"GET",success:function(i){t.level3Sources=e(t,$(i).find("CSV").text(),t.fields),t.view.requestRedraw()},error:function(t){console.log("Something went wrong: "+t)}})},draw:function(t,e,i,o,r,s,a){if(this.isShowing&&this.level3Sources&&(this.drawSources(this.level2Sources,t,e,i,o,r,s,a),this.drawSources(this.level3Sources,t,e,i,o,r,s,a),this.tilesInView))for(var n,h,l,c=0;this.tilesInView.length>c;c++)l=this.tilesInView[c],h=l[0]+"-"+l[1],n=this.sourcesCache.get(h),n&&this.drawSources(n,t,e,i,o,r,s,a)},drawSources:function(t,e,i,o,r,s,a,n){for(var h=0,l=t.length;l>h;h++)this.drawSource(t[h],e,i,o,r,s,a,n)},getSources:function(){var t=[];if(this.level2Sources&&(t=t.concat(this.level2Sources)),this.level3Sources&&(t=t.concat(this.level3Sources)),this.tilesInView)for(var e,i,o,r=0;this.tilesInView.length>r;r++)o=this.tilesInView[r],i=o[0]+"-"+o[1],e=this.sourcesCache.get(i),e&&(t=t.concat(e));return t},drawSource:function(t,e,i,o,r,s,a,n){if(t.isShowing){var h,l=this.sourceSize;if(o!=CooFrameEnum.J2000){var c=CooConversion.J2000ToGalactic([t.ra,t.dec]);h=i.project(c[0],c[1])}else h=i.project(t.ra,t.dec);if(h){var u=AladinUtils.xyToView(h.X,h.Y,r,s,a,n);if(u){if(u.vx>r+l||0-l>u.vx||u.vy>s+l||0-l>u.vy)return t.x=t.y=void 0,void 0;t.x=u.vx,t.y=u.vy,e.drawImage(this.cacheCanvas,t.x-l/2,t.y-l/2)}}}},deselectAll:function(){for(var t=0;this.level2Sources.length>t;t++)this.level2Sources[t].deselect();for(var t=0;this.level3Sources.length>t;t++)this.level3Sources[t].deselect();var e=this.sourcesCache.keys();for(key in e)if(this.sourcesCache[key])for(var i=this.sourcesCache[key],t=0;i.length>t;t++)i[t].deselect()},show:function(){this.isShowing||(this.isShowing=!0,this.reportChange())},hide:function(){this.isShowing&&(this.isShowing=!1,this.reportChange())},reportChange:function(){this.view.requestRedraw()},getTileURL:function(t,e){var i=1e4*Math.floor(e/1e4);return this.rootUrl+"/"+"Norder"+t+"/Dir"+i+"/Npix"+e+".tsv"},loadNeededTiles:function(){this.tilesInView=[],this.otherSources=[];var t=this.view.realNorder;if(t>this.maxOrder&&(t=this.maxOrder),!(3>=t)){for(var i,o,r=this.view.getVisibleCells(t,this.frame),s=4;t>=s;s++){i=[];for(var a=0;r.length>a;a++)o=Math.floor(r[a].ipix/Math.pow(4,t-s)),0>i.indexOf(o)&&i.push(o);for(var n=0;i.length>n;n++)this.tilesInView.push([s,i[n]])}for(var h,l,a=0;this.tilesInView.length>a;a++)h=this.tilesInView[a],l=h[0]+"-"+h[1],this.sourcesCache.get(l)||function(t,i,o){var r=i+"-"+o;$.ajax({url:t.getTileURL(i,o),method:"GET",success:function(i){t.sourcesCache.set(r,e(t,i,t.fields)),t.view.requestRedraw()},error:function(){t.sourcesCache.set(r,[])}})}(this,h[0],h[1])}}},ProgressiveCat}(),cds.Catalog=function(){return cds.Catalog=function(t){t=t||{},this.type="catalog",this.name=t.name||"catalog",this.color=t.color||Color.getNextColor(),this.sourceSize=t.sourceSize||6,this.selectSize=this.sourceSize+2,this.isShowing=!0,this.indexationNorder=5,this.sources=[],this.hpxIdx=new HealpixIndex(this.indexationNorder),this.hpxIdx.init(),this.selectionColor="#00ff00",this.cacheCanvas=document.createElement("canvas"),this.cacheCanvas.width=this.sourceSize,this.cacheCanvas.height=this.sourceSize;var e=this.cacheCanvas.getContext("2d");e.beginPath(),e.strokeStyle=this.color,e.lineWidth=2,e.moveTo(0,0),e.lineTo(0,this.sourceSize),e.lineTo(this.sourceSize,this.sourceSize),e.lineTo(this.sourceSize,0),e.lineTo(0,0),e.stroke(),this.cacheMarkerCanvas=document.createElement("canvas"),this.cacheMarkerCanvas.width=this.sourceSize,this.cacheMarkerCanvas.height=this.sourceSize;var i=this.cacheMarkerCanvas.getContext("2d");i.fillStyle=this.color,i.beginPath();var o=this.sourceSize/2;i.arc(o,o,o-2,0,2*Math.PI,!1),i.fill(),i.lineWidth=2,i.strokeStyle="#ccc",i.stroke(),this.cacheSelectCanvas=document.createElement("canvas"),this.cacheSelectCanvas.width=this.selectSize,this.cacheSelectCanvas.height=this.selectSize;var r=this.cacheSelectCanvas.getContext("2d");r.beginPath(),r.strokeStyle=this.selectionColor,r.lineWidth=2,r.moveTo(0,0),r.lineTo(0,this.selectSize),r.lineTo(this.selectSize,this.selectSize),r.lineTo(this.selectSize,0),r.lineTo(0,0),r.stroke()},cds.Catalog.parseVOTable=function(t,e){function i(t,e){t=t.replace(/^\s+/g,"");var i=["name","ID","ucd","utype","unit","datatype","arraysize","width","precision"],o=[],r=0;$(t).find("FIELD").each(function(){for(var t={},e=0;i.length>e;e++){var s=i[e];$(this).attr(s)&&(t[s]=$(this).attr(s))}t.ID||(t.ID="col_"+r),o.push(t),r++});var s,a;s=a=null;for(var n=0,h=o.length;h>n;n++){var l=o[n];if(!s&&l.ucd){var c=l.ucd.toLowerCase();if(c.indexOf("pos.eq.ra")>=0||c.indexOf("pos_eq_ra")>=0){s=n;continue}}if(!a&&l.ucd){var c=l.ucd.toLowerCase();if(c.indexOf("pos.eq.dec")>=0||c.indexOf("pos_eq_dec")>=0){a=n;continue}}}var u,d,p=[],f=new Coo;$(t).find("TR").each(function(){var t={},e=0;$(this).find("TD").each(function(){var i=o[e].name?o[e].name:o[e].id;t[i]=$(this).text(),e++});var i=o[s].name?o[s].name:o[s].id,r=o[a].name?o[a].name:o[a].id;Utils.isNumber(t[i])&&Utils.isNumber(t[r])?(u=parseFloat(t[i]),d=parseFloat(t[r])):(f.parse(t[i]+" "+t[r]),u=f.lon,d=f.lat),p.push(new cds.Source(u,d,t))}),e&&e(p)}$.ajax({url:Aladin.JSONP_PROXY,data:{url:t},method:"GET",dataType:"jsonp",success:function(t){i(t,e)}})},cds.Catalog.prototype.addSources=function(t){this.sources=this.sources.concat(t);for(var e=0,i=t.length;i>e;e++)t[e].setCatalog(this);this.view.requestRedraw()},cds.Catalog.prototype.getSources=function(){return this.sources},cds.Catalog.prototype.selectAll=function(){if(this.sources)for(var t=0;this.sources.length>t;t++)this.sources[t].select()},cds.Catalog.prototype.deselectAll=function(){if(this.sources)for(var t=0;this.sources.length>t;t++)this.sources[t].deselect()},cds.Catalog.prototype.getSource=function(t){return this.sources.length>t?this.sources[t]:null},cds.Catalog.prototype.setView=function(t){this.view=t},cds.Catalog.prototype.removeAll=function(){this.sources=[]},cds.Catalog.prototype.draw=function(t,e,i,o,r,s,a){if(this.isShowing){for(var n=0,h=this.sources.length;h>n;n++)this.drawSource(this.sources[n],t,e,i,o,r,s,a);t.strokeStyle=this.selectionColor,t.beginPath();for(var n=0,h=this.sources.length;h>n;n++)this.sources[n].isSelected&&this.drawSourceSelection(this.sources[n],t);t.stroke()}},cds.Catalog.prototype.drawSource=function(t,e,i,o,r,s,a,n){if(t.isShowing){var h,l=this.sourceSize;if(o!=CooFrameEnum.J2000){var c=CooConversion.J2000ToGalactic([t.ra,t.dec]);h=i.project(c[0],c[1])}else h=i.project(t.ra,t.dec);if(h){var u=AladinUtils.xyToView(h.X,h.Y,r,s,a,n),d=t.popup?100:t.sourceSize;if(u){if(u.vx>r+d||0-d>u.vx||u.vy>s+d||0-d>u.vy)return t.x=t.y=void 0,void 0;t.x=u.vx,t.y=u.vy,t.marker?e.drawImage(this.cacheMarkerCanvas,t.x-l/2,t.y-l/2):e.drawImage(this.cacheCanvas,t.x-l/2,t.y-l/2),t.popup&&t.popup.setPosition(t.x,t.y)}}}},cds.Catalog.prototype.drawSourceSelection=function(t,e){if(t&&t.isShowing&&t.x&&t.y){var i=this.selectSize;e.drawImage(this.cacheSelectCanvas,t.x-i/2,t.y-i/2)}},cds.Catalog.prototype.reportChange=function(){this.view.requestRedraw()},cds.Catalog.prototype.show=function(){this.isShowing||(this.isShowing=!0,this.reportChange())},cds.Catalog.prototype.hide=function(){this.isShowing&&(this.isShowing=!1,this.view&&this.view.popup&&this.view.popup.source&&this.view.popup.source.catalog==this&&this.view.popup.hide(),this.reportChange())},cds.Catalog}(),Tile=function(){function t(t,e){this.img=t,this.url=e}return t.isImageOk=function(t){return t.allSkyTexture?!0:t.src?t.complete?t.naturalWidth!==void 0&&0==t.naturalWidth?!1:!0:!1:!1},t}(),TileBuffer=function(){function t(){this.pointer=0,this.tilesMap={},this.tilesArray=Array(e);for(var t=0;e>t;t++)this.tilesArray[t]=new Tile(new Image,null)}var e=800;return t.prototype.addTile=function(t){if(this.getTile(t))return null;var i=this.tilesArray[this.pointer];return null!=i.url&&(i.img.src=null,delete this.tilesMap[i.url]),this.tilesArray[this.pointer].url=t,this.tilesMap[t]=this.tilesArray[this.pointer],this.pointer++,this.pointer>=e&&(this.pointer=0),this.tilesMap[t]},t.prototype.getTile=function(t){return this.tilesMap[t]},t}(),ColorMap=function(){return ColorMap=function(t){this.view=t,this.reversed=!1,this.map="native",this.sig=this.signature()},ColorMap.MAPS={},ColorMap.MAPS.eosb={name:"Eos B",r:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,18,27,36,45,49,57,72,81,91,100,109,118,127,136,131,139,163,173,182,191,200,209,218,227,213,221,255,255,255,255,255,255,255,255,229,229,255,255,255,255,255,255,255,255,229,229,255,255,255,255,255,255,255,255,229,229,255,255,255,255,255,255,255,255,229,229,255,255,255,255,255,255,255,255,229,229,255,255,255,255,255,255,255,255,229,229,255,255,255,255,255,255,255,255,229,229,255,255,255,255,255,255,255,255,229,229,255,255,255,255,255,255,255,255,229,229,255,253,251,249,247,245,243,241,215,214,235,234,232,230,228,226,224,222,198,196,216,215,213,211,209,207,205,203,181,179,197,196,194,192,190,188,186,184,164,162,178,176,175,173,171,169,167,165,147,145,159,157,156,154,152,150,148,146,130,128,140,138,137,135,133,131,129,127,113,111,121,119,117,117],g:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,15,23,31,39,47,55,57,64,79,87,95,103,111,119,127,135,129,136,159,167,175,183,191,199,207,215,200,207,239,247,255,255,255,255,255,255,229,229,255,255,255,255,255,255,255,255,229,229,255,255,255,255,255,255,255,255,229,229,255,250,246,242,238,233,229,225,198,195,212,208,204,199,195,191,187,182,160,156,169,165,161,157,153,148,144,140,122,118,127,125,123,121,119,116,114,112,99,97,106,104,102,99,97,95,93,91,80,78,84,82,80,78,76,74,72,70,61,59,63,61,59,57,55,53,50,48,42,40,42,40,38,36,33,31,29,27,22,21,21,19,16,14,12,13,8,6,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],b:[116,121,127,131,136,140,144,148,153,157,145,149,170,174,178,182,187,191,195,199,183,187,212,216,221,225,229,233,238,242,221,225,255,247,239,231,223,215,207,199,172,164,175,167,159,151,143,135,127,119,100,93,95,87,79,71,63,55,47,39,28,21,15,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},ColorMap.MAPS.rainbow={name:"Rainbow",r:[0,4,9,13,18,22,27,31,36,40,45,50,54,58,61,64,68,69,72,74,77,79,80,82,83,85,84,86,87,88,86,87,87,87,85,84,84,84,83,79,78,77,76,71,70,68,66,60,58,55,53,46,43,40,36,33,25,21,16,12,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,8,12,21,25,29,33,42,46,51,55,63,67,72,76,80,89,93,97,101,110,114,119,123,131,135,140,144,153,157,161,165,169,178,182,187,191,199,203,208,212,221,225,229,233,242,246,250,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],g:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,8,16,21,25,29,38,42,46,51,55,63,67,72,76,84,89,93,97,106,110,114,119,127,131,135,140,144,152,157,161,165,174,178,182,187,195,199,203,208,216,220,225,229,233,242,246,250,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,250,242,238,233,229,221,216,212,208,199,195,191,187,178,174,170,165,161,153,148,144,140,131,127,123,119,110,106,102,97,89,85,80,76,72,63,59,55,51,42,38,34,29,21,17,12,8,0],b:[0,3,7,10,14,19,23,28,32,38,43,48,53,59,63,68,72,77,81,86,91,95,100,104,109,113,118,122,127,132,136,141,145,150,154,159,163,168,173,177,182,186,191,195,200,204,209,214,218,223,227,232,236,241,245,250,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,246,242,238,233,225,220,216,212,203,199,195,191,187,178,174,170,165,157,152,148,144,135,131,127,123,114,110,106,102,97,89,84,80,76,67,63,59,55,46,42,38,34,25,21,16,12,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},ColorMap.MAPS_CUSTOM=["rainbow","eosb"],ColorMap.MAPS_NAMES=["native","grayscale"].concat(ColorMap.MAPS_CUSTOM),ColorMap.prototype.reverse=function(t){this.reversed=t?t:!this.reversed,this.sig=this.signature(),this.view.requestRedraw()},ColorMap.prototype.signature=function(){var t=this.map;return this.reversed&&(t+=" reversed"),t},ColorMap.prototype.update=function(t){this.map=t,this.sig=this.signature(),this.view.requestRedraw()},ColorMap.prototype.apply=function(t){if("native"==this.sig)return t;if(t.cmSig==this.sig)return t.cmImg;var e=document.createElement("canvas");e.width=t.width,e.height=t.height;var i=e.getContext("2d");i.drawImage(t,0,0);var o,r,s,a=i.getImageData(0,0,e.width,e.height),n=a.data,h=n.length,l=3;"grayscale"==this.map?l=1:ColorMap.MAPS_CUSTOM.indexOf(this.map)>=0&&(l=2);for(var c=0;h>c;c+=4){switch(l){case 1:o=r=s=AladinUtils.myRound((n[c]+n[c+1]+n[c+2])/3);break;case 2:this.reversed?(o=ColorMap.MAPS[this.map].r[255-n[c]],r=ColorMap.MAPS[this.map].g[255-n[c+1]],s=ColorMap.MAPS[this.map].b[255-n[c+2]]):(o=ColorMap.MAPS[this.map].r[n[c]],r=ColorMap.MAPS[this.map].g[n[c+1]],s=ColorMap.MAPS[this.map].b[n[c+2]]);break;default:o=n[c],r=n[c+1],s=n[c+2]}2!=l&&this.reversed&&(o=255-o,r=255-r,s=255-s),n[c]=o,n[c+1]=r,n[c+2]=s}return a.data=n,i.putImageData(a,0,0),t.cmSig=this.sig,t.cmImg=e,t.cmImg},ColorMap}(),HpxImageSurvey=function(){function t(t,e,i,o,r,s,a,n,h,l,c,u,d,p,f,v,g,m){v=v||0,g=g||0,m||(m=!1),h+=v,c+=v,d+=v,l+=g,u+=g,p+=g;var y=(i+r+a)/3,w=(o+s+n)/3,y=(i+r+a)/3,w=(o+s+n)/3;t.save(),f&&(t.globalAlpha=f);var C=.01;m&&(C=.01),t.beginPath(),t.moveTo((1+C)*i-y*C,(1+C)*o-w*C),t.lineTo((1+C)*r-y*C,(1+C)*s-w*C),t.lineTo((1+C)*a-y*C,(1+C)*n-w*C),t.closePath(),t.clip(),m&&(C=.03,i=(1+C)*i-y*C,o=(1+C)*o-w*C,r=(1+C)*r-y*C,s=(1+C)*s-w*C,a=(1+C)*a-y*C,n=(1+C)*n-w*C);var x=1/(h*(p-u)-c*p+d*u+(c-d)*l);t.transform(-(l*(a-r)-u*a+p*r+(u-p)*i)*x,(u*n+l*(s-n)-p*s+(p-u)*o)*x,(h*(a-r)-c*a+d*r+(c-d)*i)*x,-(c*n+h*(s-n)-d*s+(d-c)*o)*x,(h*(p*r-u*a)+l*(c*a-d*r)+(d*u-c*p)*i)*x,(h*(p*s-u*n)+l*(c*n-d*s)+(d*u-c*p)*o)*x),t.drawImage(e,0,0),t.restore()}var e=function(t,i,o,r,s,a){this.id=t,this.name=i,this.rootUrl="/"===o.slice(-1)?o.substr(0,o.length-1):o,a=a||{},this.imgFormat=a.imgFormat||"jpg",this.minOrder=a.minOrder||null,this.cooFrame=CooFrameEnum.fromString(r,CooFrameEnum.J2000),this.rootUrl.indexOf("/glimpse360/aladin/data")>=0&&(this.cooFrame=CooFrameEnum.J2000),this.maxOrder=s,this.allskyTexture=null,this.alpha=0,this.allskyTextureSize=0,this.lastUpdateDateNeededTiles=0;
for(var n=!1,h=0;e.SURVEYS.length>h;h++)e.SURVEYS[h].id==this.id&&(n=!0);n||e.SURVEYS.push({id:this.id,url:this.rootUrl,name:this.name,maxOrder:this.maxOrder,frame:this.cooFrame}),e.SURVEYS_OBJECTS[this.id]=this};e.UPDATE_NEEDED_TILES_DELAY=1e3,e.prototype.init=function(t,e){this.view=t,this.cm||(this.cm=new ColorMap(this.view)),this.tileBuffer=this.view.tileBuffer,this.useCors=!1;var i=this;$.support.cors?$.ajax({type:"GET",url:this.rootUrl+"/properties",contentType:"text/plain",xhrFields:{},headers:{},success:function(){i.useCors=!0,i.retrieveAllskyTextures(),e&&e()},error:function(){i.retrieveAllskyTextures(),e&&e()}}):(this.retrieveAllskyTextures(),e())},e.DEFAULT_SURVEY_ID="P/DSS2/color",e.SURVEYS_OBJECTS={},e.SURVEYS=[{id:"P/2MASS/color",url:"http://alasky.u-strasbg.fr/2MASS/Color",name:"2MASS colored",maxOrder:9,frame:"equatorial",format:"jpeg"},{id:"P/DSS2/color",url:"http://alasky.u-strasbg.fr/DssColor",name:"DSS colored",maxOrder:9,frame:"equatorial",format:"jpeg"},{id:"P/DSS2/red",url:"http://alasky.u-strasbg.fr/DSS/DSS2Merged",name:"DSS2 Red (F+R)",maxOrder:9,frame:"equatorial",format:"jpeg fits"},{id:"P/Fermi/color",url:"http://alasky.u-strasbg.fr/Fermi/Color",name:"Fermi color",maxOrder:3,frame:"equatorial",format:"jpeg"},{id:"P/Finkbeiner",url:"http://alasky.u-strasbg.fr/FinkbeinerHalpha",maxOrder:3,frame:"galactic",format:"jpeg fits",name:"Halpha"},{id:"P/GALEXGR6/AIS/color",url:"http://alasky.u-strasbg.fr/GALEX/GR6-02-Color",name:"GALEX Allsky Imaging Survey colored",maxOrder:8,frame:"equatorial",format:"jpeg"},{id:"P/IRIS/color",url:"http://alasky.u-strasbg.fr/IRISColor",name:"IRIS colored",maxOrder:3,frame:"galactic",format:"jpeg"},{id:"P/Mellinger/color",url:"http://alasky.u-strasbg.fr/MellingerRGB",name:"Mellinger colored",maxOrder:4,frame:"galactic",format:"jpeg"},{id:"P/SDSS9/color",url:"http://alasky.u-strasbg.fr/SDSS/DR9/color",name:"SDSS9 colored",maxOrder:10,frame:"equatorial",format:"jpeg"},{id:"P/SPITZER/color",url:"http://alasky.u-strasbg.fr/SpitzerI1I2I4color",name:"IRAC color I1,I2,I4 - (GLIMPSE, SAGE, SAGE-SMC, SINGS)",maxOrder:9,frame:"galactic",format:"jpeg"},{id:"P/VTSS/Ha",url:"http://alasky.u-strasbg.fr/VTSS/Ha",maxOrder:3,frame:"galactic",format:"png jpeg fits",name:"VTSS-Ha"},{id:"P/XMM/EPIC",url:"http://saada.u-strasbg.fr/xmmallsky",name:"XMM-Newton stacked EPIC images (no phot. normalization)",maxOrder:7,frame:"equatorial",format:"png jpeg fits"},{id:"P/XMM/PN/color",url:"http://saada.unistra.fr/xmmpnsky",name:"XMM PN colored",maxOrder:7,frame:"equatorial",format:"png jpeg"}],e.getAvailableSurveys=function(){return e.SURVEYS},e.getSurveyInfoFromId=function(t){for(var i=e.getAvailableSurveys(),o=0;i.length>o;o++)if(i[o].id==t)return i[o];return null},e.getSurveyFromId=function(t){if(e.SURVEYS_OBJECTS[t])return e.SURVEYS_OBJECTS[t];var i=e.getSurveyInfoFromId(t);return i?new e(i.id,i.name,i.url,i.frame,i.maxOrder):null},e.prototype.getTileURL=function(t,e){var i=1e4*Math.floor(e/1e4);return this.rootUrl+"/"+"Norder"+t+"/Dir"+i+"/Npix"+e+"."+this.imgFormat},e.prototype.retrieveAllskyTextures=function(){var t=new Image;this.useCors&&(t.crossOrigin="anonymous");var e=this;t.onload=function(){e.allskyTextureSize=t.width/27,e.allskyTexture=t,e.view.requestRedraw()},t.src=this.rootUrl+"/Norder3/Allsky."+this.imgFormat},e.prototype.redrawAllsky=function(t,e,i){if(!(this.view.curNorder>6)&&this.allskyTexture)for(var o,r,s,a=0,n=0,h=e.length;h>n;n++)if(o=e[n],s=o.ipix,this.allskyTexture&&Tile.isImageOk(this.allskyTexture)){var l=this.allskyTextureSize*Math.floor(s/27),c=this.allskyTextureSize*(s-27*Math.floor(s/27));if(i>40){a=.02,a=0,r={x:(o[0].vx+o[2].vx)/2,y:(o[0].vy+o[2].vy)/2};for(var u=0;4>u;u++){var d={x:o[u].vx-r.x,y:o[u].vy-r.y};o[u].vx+=a*d.x,o[u].vy+=a*d.y}}this.drawOneTile(t,this.allskyTexture,o,this.allskyTextureSize,null,c,l,!0)}},e.prototype.getColorMap=function(){return this.cm};var i=!0;return e.prototype.redrawHighres=function(t,o,r){i=!i;var s,a,n,h,l,c,u,d,p=(new Date).getTime(),f=p-this.lastUpdateDateNeededTiles>e.UPDATE_NEEDED_TILES_DELAY,v=r-1,g=[],m=[],y={},w=!1,C=[],x=[];if(f){var S=[(o[0][0].vx+o[0][1].vx)/2,(o[0][0].vy+o[0][1].vy)/2],M=o.sort(function(t,e){var i=[(t[0].vx+t[2].vx)/2,(t[0].vy+t[2].vy)/2],o=[(e[0].vx+e[2].vx)/2,(e[0].vy+e[2].vy)/2],r=(i[0]-S[0])*(i[0]-S[0])+(i[1]-S[1])*(i[1]-S[1]),s=(o[0]-S[0])*(o[0]-S[0])+(o[1]-S[1])*(o[1]-S[1]);return r-s});o=M}for(var b=0,T=o.length;T>b;b++)if(l=o[b],d=l.ipix,u=~~(d/4),h=this.getTileURL(v,u),f&&v>=3&&(n=this.tileBuffer.addTile(h),n&&x.push({img:n.img,url:h})),a=this.getTileURL(r,d),s=this.tileBuffer.getTile(a))Tile.isImageOk(s.img)?g.push({img:s.img,corners:l}):(w=!0,f&&!s.img.dlError&&C.push({img:s.img,url:a}),v>=3&&!y[u]&&(n=this.tileBuffer.getTile(h),n&&Tile.isImageOk(n.img)&&(c=this.view.getPositionsInView(u,v),c&&m.push({img:n.img,corners:c,ipix:u})),y[u]=1));else{if(w=!0,f){var s=this.tileBuffer.addTile(a);s&&C.push({img:s.img,url:a})}v>=3&&!y[u]&&(n=this.tileBuffer.getTile(h),n&&Tile.isImageOk(n.img)&&(c=this.view.getPositionsInView(u,v),c&&m.push({img:n.img,corners:c,ipix:u})),y[u]=1)}for(var b=0,T=m.length;T>b;b++)this.drawOneTile(t,m[b].img,m[b].corners,m[b].img.width);for(var b=0,T=g.length;T>b;b++){var I=null,P=g[b].img;P.fadingStart&&P.fadingEnd&&P.fadingEnd>p&&(I=.2+.8*((p-P.fadingStart)/(P.fadingEnd-P.fadingStart))),this.drawOneTile(t,P,g[b].corners,P.width,I)}if(f){for(var b=0,T=C.length;T>b;b++)this.view.downloader.requestDownload(C[b].img,C[b].url,this.useCors);for(var b=0,T=x.length;T>b;b++)this.view.downloader.requestDownload(x[b].img,x[b].url,this.useCors);this.lastUpdateDateNeededTiles=p}w&&this.view.requestRedrawAtDate(p+e.UPDATE_NEEDED_TILES_DELAY+10)},e.prototype.drawOneTile=function(e,i,o,r,s,a,n,h){var l=this.useCors?this.cm.apply(i):i;t(e,l,o[0].vx,o[0].vy,o[1].vx,o[1].vy,o[3].vx,o[3].vy,r-1,r-1,r-1,0,0,r-1,s,a,n,h),t(e,l,o[1].vx,o[1].vy,o[3].vx,o[3].vy,o[2].vx,o[2].vy,r-1,0,0,r-1,0,0,s,a,n,h)},e.prototype.setAlpha=function(t){t=+t,this.alpha=Math.max(0,Math.min(t,1)),this.view.requestRedraw()},e.prototype.getAlpha=function(){return this.alpha},e}(),HealpixGrid=function(){var t=function(){};return t.prototype.redraw=function(t,e,i,o){t.lineWidth=1,t.strokeStyle="rgb(150,150,220)",t.beginPath();for(var r,s=0,a=e.length;a>s;s++)r=e[s],ipix=r.ipix,t.moveTo(r[0].vx,r[0].vy),t.lineTo(r[1].vx,r[1].vy),t.lineTo(r[2].vx,r[2].vy);t.stroke(),t.strokeStyle="#FFDDDD",t.beginPath();for(var s=0,a=e.length;a>s;s++)r=e[s],ipix=r.ipix,t.strokeText(o+"/"+ipix,(r[0].vx+r[2].vx)/2,(r[0].vy+r[2].vy)/2);t.stroke()},t}(),Location=function(){return Location=function(t){this.div=$(t)},Location.prototype.update=function(t,e,i){var o=new Coo(t,e,7);i==CooFrameEnum.J2000?this.div.html(o.format("s/")):this.div.html(o.format("d/"))},Location}(),View=function(){function t(e,i,o,r,s){this.aladin=e,this.options=e.options,this.aladinDiv=this.aladin.aladinDiv,this.popup=new Popup(this.aladinDiv),this.createCanvases(),this.location=i,this.fovDiv=o,this.mustClearCatalog=!0,this.mustRedrawReticle=!0,this.mode=t.PAN,this.minFOV=this.maxFOV=null,this.healpixGrid=new HealpixGrid(this.imageCanvas),this.cooFrame=r?r:CooFrameEnum.GAL;var a,n;a=n=0,this.projectionMethod=ProjectionEnum.SIN,this.projection=new Projection(a,n),this.projection.setProjection(this.projectionMethod),this.zoomLevel=0,this.zoomFactor=this.computeZoomFactor(this.zoomLevel),this.viewCenter={lon:a,lat:n},s&&this.setZoom(s),this.imageSurvey=null,this.catalogs=[],this.overlays=[],this.tileBuffer=new TileBuffer,this.fixLayoutDimensions(),this.curNorder=1,this.realNorder=1,this.dragging=!1,this.dragx=null,this.dragy=null,this.needRedraw=!0,this.downloader=new Downloader(this),this.flagForceRedraw=!1,this.fadingLatestUpdate=null,this.dateRequestRedraw=null,init(this),this.resizeTimer=null;var h=this;$(window).resize(function(){clearTimeout(h.resizeTimer),h.resizeTimer=setTimeout(function(){h.fixLayoutDimensions(h)},100)})}function e(t,e,i,o){if(t.projection){var r,s=AladinUtils.viewToXy(e,i,t.width,t.height,t.largestDim,t.zoomFactor);try{r=t.projection.unproject(s.x,s.y)}catch(a){}r&&t.location.update(r.ra,r.dec,t.cooFrame,o)}}return t.PAN=0,t.SELECT=1,t.DRAW_SOURCES_WHILE_DRAGGING=!0,t.prototype.createCanvases=function(){var t=$(this.aladinDiv);t.find(".aladin-imageCanvas").remove(),t.find(".aladin-catalogCanvas").remove(),t.find(".aladin-reticleCanvas").remove(),this.imageCanvas=$("<canvas class='aladin-imageCanvas'></canvas>").appendTo(this.aladinDiv)[0],this.catalogCanvas=$("<canvas class='aladin-catalogCanvas'></canvas>").appendTo(this.aladinDiv)[0],this.reticleCanvas=$("<canvas class='aladin-reticleCanvas'></canvas>").appendTo(this.aladinDiv)[0]},t.prototype.fixLayoutDimensions=function(){Utils.cssScale=void 0,this.width=$(this.aladinDiv).width(),this.height=$(this.aladinDiv).height(),this.cx=this.width/2,this.cy=this.height/2,this.largestDim=Math.max(this.width,this.height),this.smallestDim=Math.min(this.width,this.height),this.ratio=this.largestDim/this.smallestDim,this.mouseMoveIncrement=160/this.largestDim,this.imageCtx=this.imageCanvas.getContext("2d"),this.catalogCtx=this.catalogCanvas.getContext("2d"),this.reticleCtx=this.reticleCanvas.getContext("2d"),this.imageCtx.canvas.width=this.width,this.catalogCtx.canvas.width=this.width,this.reticleCtx.canvas.width=this.width,this.imageCtx.canvas.height=this.height,this.catalogCtx.canvas.height=this.height,this.reticleCtx.canvas.height=this.height,this.computeNorder(),this.requestRedraw()},t.prototype.setMode=function(e){this.mode=e,this.mode==t.SELECT?this.setCursor("crosshair"):this.setCursor("default")},t.prototype.setCursor=function(t){this.reticleCanvas.style.cursor!=t&&(this.reticleCanvas.style.cursor=t)},t.prototype.getCanvasDataURL=function(){var t=document.createElement("canvas");t.width=this.width,t.height=this.height;var e=t.getContext("2d");return e.drawImage(this.imageCanvas,0,0),e.drawImage(this.catalogCanvas,0,0),e.drawImage(this.reticleCanvas,0,0),t.toDataURL("image/png")},computeFov=function(t){var e=doComputeFov(t,t.zoomFactor);return t.mouseMoveIncrement=e/t.imageCanvas.width,e},doComputeFov=function(t,e){if(1>t.zoomFactor)fov=180;else{var i=AladinUtils.viewToXy(0,t.cy,t.width,t.height,t.largestDim,e),o=t.projection.unproject(i.x,i.y),r=AladinUtils.viewToXy(t.imageCanvas.width-1,t.cy,t.width,t.height,t.largestDim,e),s=t.projection.unproject(r.x,r.y);fov=new Coo(o.ra,o.dec).distance(new Coo(s.ra,s.dec))}return fov},updateFovDiv=function(t){if(isNaN(t.fov))return t.fovDiv.html("FoV:"),void 0;var e;e=t.fov>1?Math.round(100*t.fov)/100+"°":60*t.fov>1?Math.round(100*60*t.fov)/100+"'":Math.round(100*3600*t.fov)/100+'"',t.fovDiv.html("FoV: "+e)},createListeners=function(i){var o=!1;"ontouchstart"in window&&(o=!0),onDblClick=function(t){var e=i.imageCanvas.relMouseCoords(t),o=AladinUtils.viewToXy(e.x,e.y,i.width,i.height,i.largestDim,i.zoomFactor);try{var r=i.projection.unproject(o.x,o.y)}catch(s){return}radec=[],radec=i.cooFrame==CooFrameEnum.GAL?CooConversion.GalacticToJ2000([r.ra,r.dec]):[r.ra,r.dec],i.pointTo(radec[0],radec[1])},o||$(i.reticleCanvas).dblclick(onDblClick),$(i.reticleCanvas).bind("mousedown touchstart",function(e){var o=i.imageCanvas.relMouseCoords(e);return e.originalEvent&&e.originalEvent.targetTouches?(i.dragx=e.originalEvent.targetTouches[0].clientX,i.dragy=e.originalEvent.targetTouches[0].clientY):(i.dragx=o.x,i.dragy=o.y),i.dragging=!0,i.mode==t.PAN?i.setCursor("move"):i.mode==t.SELECT&&(i.selectStartCoo={x:i.dragx,y:i.dragy}),!1}),$(i.reticleCanvas).bind("mouseup mouseout touchend",function(e){i.mode==t.SELECT&&i.dragging&&i.aladin.fire("selectend",i.getObjectsInBBox(i.selectStartCoo.x,i.selectStartCoo.y,i.dragx-i.selectStartCoo.x,i.dragy-i.selectStartCoo.y)),i.dragging&&(i.setCursor("default"),i.dragging=!1),i.mustClearCatalog=!0,i.mustRedrawReticle=!0,i.dragx=i.dragy=null;var o=i.imageCanvas.relMouseCoords(e),r=i.closestObjects(o.x,o.y,5);if(r){var s=r[0];s.marker?(i.popup.setTitle(s.popupTitle),i.popup.setText(s.popupDesc),i.popup.setSource(s),i.popup.show()):i.aladin.objClickedFunction&&i.aladin.objClickedFunction(s)}"mouseout"!==e.type&&i.refreshProgressiveCats(),i.requestRedraw()}),$(i.reticleCanvas).bind("mousemove touchmove",function(r){r.preventDefault();var s=i.imageCanvas.relMouseCoords(r);if(!i.dragging||o){if(e(i,s.x,s.y,!0),!i.dragging&&!i.mode==t.SELECT){var a=i.closestObjects(s.x,s.y,5);a?(i.setCursor("pointer"),i.aladin.objHoveredFunction&&i.aladin.objHoveredFunction(a[0])):i.setCursor("default")}if(!o)return}var n,h,l,c;if(r.originalEvent&&r.originalEvent.targetTouches){n=r.originalEvent.targetTouches[0].clientX-i.dragx,h=r.originalEvent.targetTouches[0].clientY-i.dragy;var u=AladinUtils.viewToXy(r.originalEvent.targetTouches[0].clientX,r.originalEvent.targetTouches[0].clientY,i.width,i.height,i.largestDim,i.zoomFactor),d=AladinUtils.viewToXy(i.dragx,i.dragy,i.width,i.height,i.largestDim,i.zoomFactor);l=i.projection.unproject(u.x,u.y),c=i.projection.unproject(d.x,d.y)}else{n=s.x-i.dragx,h=s.y-i.dragy;var u=AladinUtils.viewToXy(s.x,s.y,i.width,i.height,i.largestDim,i.zoomFactor),d=AladinUtils.viewToXy(i.dragx,i.dragy,i.width,i.height,i.largestDim,i.zoomFactor);l=i.projection.unproject(u.x,u.y),c=i.projection.unproject(d.x,d.y)}return r.originalEvent&&r.originalEvent.targetTouches?(i.dragx=r.originalEvent.targetTouches[0].clientX,i.dragy=r.originalEvent.targetTouches[0].clientY):(i.dragx=s.x,i.dragy=s.y),i.mode==t.SELECT?(i.requestRedraw(),void 0):(i.viewCenter.lon+=c.ra-l.ra,i.viewCenter.lat+=c.dec-l.dec,i.viewCenter.lat>90?i.viewCenter.lat=90:-90>i.viewCenter.lat&&(i.viewCenter.lat=-90),0>i.viewCenter.lon?i.viewCenter.lon=360+i.viewCenter.lon:i.viewCenter.lon>360&&(i.viewCenter.lon=i.viewCenter.lon%360),i.requestRedraw(),void 0)}),$(i.aladinDiv).onselectstart=function(){return!1},$(i.reticleCanvas).bind("mousewheel",function(t,e){t.preventDefault(),t.stopPropagation();var o=i.zoomLevel;return e>0?o+=1:o-=1,i.setZoomLevel(o),!1})},init=function(t){var e=new Stats;e.domElement.style.top="50px",$("#aladin-statsDiv").length>0&&$("#aladin-statsDiv")[0].appendChild(e.domElement),t.stats=e,createListeners(t),t.displayHpxGrid=!1,t.displaySurvey=!0,t.displayCatalog=!1,t.displayReticle=!0,t.fov=computeFov(t),updateFovDiv(t),t.redraw()},t.prototype.requestRedrawAtDate=function(t){this.dateRequestDraw=t},t.prototype.redraw=function(){var e=this.needRedraw;requestAnimFrame(this.redraw.bind(this));var i=(new Date).getTime();if(this.dateRequestDraw&&i>this.dateRequestDraw)this.dateRequestDraw=null;else if(!this.needRedraw){if(!this.flagForceRedraw)return;this.flagForceRedraw=!1}this.stats.update();var o=this.imageCtx;o.clearRect(0,0,this.imageCanvas.width,this.imageCanvas.height),this.projectionMethod==ProjectionEnum.SIN&&(this.fov>80?(o.fillStyle="rgb(0,0,0)",o.beginPath(),o.arc(this.cx,this.cy,this.cx*this.zoomFactor,0,2*Math.PI,!0),o.fill()):60>this.fov&&(o.fillStyle="rgb(0,0,0)",o.fillRect(0,0,this.imageCanvas.width,this.imageCanvas.height))),this.projection?this.projection.setCenter(this.viewCenter.lon,this.viewCenter.lat):this.projection=new Projection(this.viewCenter.lon,this.viewCenter.lat),this.projection.setProjection(this.projectionMethod);var r=this.getVisibleCells(3),s=null;this.curNorder>=3&&(s=3==this.curNorder?r:this.getVisibleCells(this.curNorder)),this.imageSurvey&&this.imageSurvey.isReady&&this.displaySurvey&&(this.imageSurvey.redrawAllsky(o,r,this.fov,this.curNorder),this.curNorder>=3&&this.imageSurvey.redrawHighres(o,s,this.curNorder)),this.overlayImageSurvey&&this.overlayImageSurvey.isReady&&(o.globalAlpha=this.overlayImageSurvey.getAlpha(),this.fov>50&&this.overlayImageSurvey.redrawAllsky(o,r,this.fov,this.curNorder),this.curNorder>=3&&this.overlayImageSurvey.redrawHighres(o,s,this.curNorder),o.globalAlpha=1),this.displayHpxGrid&&(s&&this.curNorder>3?this.healpixGrid.redraw(o,s,this.fov,this.curNorder):this.healpixGrid.redraw(o,r,this.fov,3));var a=this.catalogCtx,n=!1;if(this.mustClearCatalog&&(a.clearRect(0,0,this.width,this.height),n=!0,this.mustClearCatalog=!1),this.catalogs&&this.catalogs.length>0&&this.displayCatalog&&(!this.dragging||t.DRAW_SOURCES_WHILE_DRAGGING)){n||(a.clearRect(0,0,this.width,this.height),n=!0);for(var h=0;this.catalogs.length>h;h++)this.catalogs[h].draw(a,this.projection,this.cooFrame,this.width,this.height,this.largestDim,this.zoomFactor,this.cooFrame)}var l=this.catalogCtx;if(this.overlays&&this.overlays.length>0&&(!this.dragging||t.DRAW_SOURCES_WHILE_DRAGGING)){n||(a.clearRect(0,0,this.width,this.height),n=!0);for(var h=0;this.overlays.length>h;h++)this.overlays[h].draw(l,this.projection,this.cooFrame,this.width,this.height,this.largestDim,this.zoomFactor,this.cooFrame)}this.mode==t.SELECT&&(mustRedrawReticle=!0);var c=this.reticleCtx;if((this.mustRedrawReticle||this.mode==t.SELECT)&&c.clearRect(0,0,this.width,this.height),this.displayReticle){if(!this.reticleCache){var u=document.createElement("canvas"),d=this.options.reticleSize;u.width=d,u.height=d;var p=u.getContext("2d");p.lineWidth=2,p.strokeStyle=this.options.reticleColor,p.beginPath(),p.moveTo(d/2,d/2+(d/2-1)),p.lineTo(d/2,d/2+2),p.moveTo(d/2,d/2-(d/2-1)),p.lineTo(d/2,d/2-2),p.moveTo(d/2+(d/2-1),d/2),p.lineTo(d/2+2,d/2),p.moveTo(d/2-(d/2-1),d/2),p.lineTo(d/2-2,d/2),p.stroke(),this.reticleCache=u}c.drawImage(this.reticleCache,this.width/2-this.reticleCache.width/2,this.height/2-this.reticleCache.height/2),this.mustRedrawReticle=!1}if(this.mode==t.SELECT&&this.dragging){c.fillStyle="rgba(100, 240, 110, 0.25)";var f=this.dragx-this.selectStartCoo.x,v=this.dragy-this.selectStartCoo.y;c.fillRect(this.selectStartCoo.x,this.selectStartCoo.y,f,v)}e==this.needRedraw&&(this.needRedraw=!1),this.dragging||this.updateObjectsLookup()},t.prototype.forceRedraw=function(){this.flagForceRedraw=!0},t.prototype.refreshProgressiveCats=function(){if(this.catalogs)for(var t=0;this.catalogs.length>t;t++)"progressivecat"==this.catalogs[t].type&&this.catalogs[t].loadNeededTiles()},t.prototype.getVisibleCells=function(t,e){!e&&this.imageSurvey&&(e=this.imageSurvey.cooFrame);var i,o=[],r=[],s=new SpatialVector,a=Math.pow(2,t),n=HealpixIndex.nside2Npix(a),h=null;if(this.fov>80){i=[];for(var l=0;n>l;l++)i.push(l)}else{var c=new HealpixIndex(a);c.init();var u=new SpatialVector,d=AladinUtils.viewToXy(this.cx,this.cy,this.width,this.height,this.largestDim,this.zoomFactor),p=this.projection.unproject(d.x,d.y),f=[];e&&e!=this.cooFrame?e==CooFrameEnum.J2000?f=CooConversion.GalacticToJ2000([p.ra,p.dec]):e==CooFrameEnum.GAL&&(f=CooConversion.J2000ToGalactic([p.ra,p.dec])):f=[p.ra,p.dec],u.set(f[0],f[1]);var v=.5*this.fov*this.ratio;v*=this.fov>60?1.6:this.fov>12?1.45:1.1,i=c.queryDisc(u,v*Math.PI/180,!0,!0);var g=Utils.radecToPolar(f[0],f[1]);h=c.ang2pix_nest(g.theta,g.phi),i.unshift(h)}for(var l,m,y,w=0,C=i.length;C>w;w++)if(l=i[w],!(l==h&&w>0)){var x=[];corners=HealpixCache.corners_nest(l,a);for(var S=0;4>S;S++){if(s.setXYZ(corners[S].x,corners[S].y,corners[S].z),e&&e!=this.cooFrame){if(e==CooFrameEnum.J2000){var p=CooConversion.J2000ToGalactic([s.ra(),s.dec()]);m=p[0],y=p[1]}else if(e==CooFrameEnum.GAL){var p=CooConversion.GalacticToJ2000([s.ra(),s.dec()]);m=p[0],y=p[1]}}else m=s.ra(),y=s.dec();r[S]=this.projection.project(m,y)}if(null!=r[0]&&null!=r[1]&&null!=r[2]&&null!=r[3]){for(var S=0;4>S;S++)x[S]=AladinUtils.xyToView(r[S].X,r[S].Y,this.width,this.height,this.largestDim,this.zoomFactor);if(!(0>x[0].vx&&0>x[1].vx&&0>x[2].vx&&0>x[3].vx||0>x[0].vy&&0>x[1].vy&&0>x[2].vy&&0>x[3].vy||x[0].vx>=this.width&&x[1].vx>=this.width&&x[2].vx>=this.width&&x[3].vx>=this.width||x[0].vy>=this.height&&x[1].vy>=this.height&&x[2].vy>=this.height&&x[3].vy>=this.height)){if(this.projection.PROJECTION==ProjectionEnum.AITOFF){var M=x[0].vx-x[2].vx,b=x[0].vy-x[2].vy,T=Math.sqrt(M*M+b*b);if(T>this.largestDim/5)continue;if(M=x[1].vx-x[3].vx,b=x[1].vy-x[3].vy,T=Math.sqrt(M*M+b*b),T>this.largestDim/5)continue}x.ipix=l,o.push(x)}}}return o},t.prototype.getPositionsInView=function(t,e){for(var i,o,r=[],s=new SpatialVector,a=Math.pow(2,e),n=[],h=HealpixCache.corners_nest(t,a),l=0;4>l;l++){if(s.setXYZ(h[l].x,h[l].y,h[l].z),this.imageSurvey&&this.imageSurvey.cooFrame!=this.cooFrame){if(this.imageSurvey.cooFrame==CooFrameEnum.J2000){var c=CooConversion.J2000ToGalactic([s.ra(),s.dec()]);i=c[0],o=c[1]}else if(this.imageSurvey.cooFrame==CooFrameEnum.GAL){var c=CooConversion.GalacticToJ2000([s.ra(),s.dec()]);i=c[0],o=c[1]}}else i=s.ra(),o=s.dec();r[l]=this.projection.project(i,o)}if(null==r[0]||null==r[1]||null==r[2]||null==r[3])return null;for(var l=0;4>l;l++)n[l]=AladinUtils.xyToView(r[l].X,r[l].Y,this.width,this.height,this.largestDim,this.zoomFactor);return n},t.prototype.computeZoomFactor=function(t){return t>0?AladinUtils.getZoomFactorForAngle(180/Math.pow(1.15,t),this.projectionMethod):1+.1*t},t.prototype.setZoom=function(t){if(!(0>t||t>180)){var e=Math.log(180/t)/Math.log(1.15);this.setZoomLevel(e)}},t.prototype.setZoomLevel=function(t){if(this.minFOV||this.maxFOV){var e=doComputeFov(this,this.computeZoomFactor(Math.max(-2,t)));if(this.maxFOV&&e>this.maxFOV||this.minFOV&&this.minFOV>e)return}if(this.zoomLevel=this.projectionMethod==ProjectionEnum.SIN?Math.max(-2,t):Math.max(-7,t),this.zoomFactor=this.computeZoomFactor(this.zoomLevel),this.fov=computeFov(this),updateFovDiv(this),this.computeNorder(),this.forceRedraw(),this.requestRedraw(),!this.debounceProgCatOnZoom){var i=this;this.debounceProgCatOnZoom=Utils.debounce(function(){i.refreshProgressiveCats()},300)}this.debounceProgCatOnZoom()},t.prototype.computeNorder=function(){var t=this.fov/this.largestDim,e=512,i=HealpixIndex.calculateNSide(3600*e*t),o=Math.log(i)/Math.log(2);o=Math.max(o,1),this.realNorder=o,50>=this.fov&&2>=o&&(o=3),this.imageSurvey&&2>=o&&this.imageSurvey.minOrder>2&&(o=this.imageSurvey.minOrder),this.imageSurvey&&o>this.imageSurvey.maxOrder&&(o=this.imageSurvey.maxOrder),o>HealpixIndex.ORDER_MAX&&(o=HealpixIndex.ORDER_MAX),this.curNorder=o},t.prototype.untaintCanvases=function(){this.createCanvases(),createListeners(this),this.fixLayoutDimensions()},t.prototype.setOverlayImageSurvey=function(t,e){if(!t)return this.overlayImageSurvey=null,this.requestRedraw(),void 0;$.support.cors&&this.overlayImageSurvey&&!this.overlayImageSurvey.useCors&&this.untaintCanvases();var i;"string"==typeof t?(i=HpxImageSurvey.getSurveyFromId(t),i||(i=HpxImageSurvey.getSurveyFromId(HpxImageSurvey.DEFAULT_SURVEY_ID))):i=t,i.isReady=!1,this.overlayImageSurvey=i;var o=this;i.init(this,function(){o.computeNorder(),i.isReady=!0,o.requestRedraw(),o.updateObjectsLookup(),e&&e()})},t.prototype.setImageSurvey=function(t,e){if(t){$.support.cors&&this.imageSurvey&&!this.imageSurvey.useCors&&this.untaintCanvases();var i;"string"==typeof t?(i=HpxImageSurvey.getSurveyFromId(t),i||(i=HpxImageSurvey.getSurveyFromId(HpxImageSurvey.DEFAULT_SURVEY_ID))):i=t,i.isReady=!1,this.imageSurvey=i;var o=this;i.init(this,function(){o.computeNorder(),i.isReady=!0,o.requestRedraw(),o.updateObjectsLookup(),e&&e()})}},t.prototype.requestRedraw=function(){this.needRedraw=!0},t.prototype.changeProjection=function(t){this.projectionMethod=t,this.requestRedraw()},t.prototype.changeFrame=function(t){if(this.cooFrame=t,this.cooFrame==CooFrameEnum.GAL){var e=CooConversion.J2000ToGalactic([this.viewCenter.lon,this.viewCenter.lat]);this.viewCenter.lon=e[0],this.viewCenter.lat=e[1]}else if(this.cooFrame==CooFrameEnum.J2000){var i=CooConversion.GalacticToJ2000([this.viewCenter.lon,this.viewCenter.lat]);this.viewCenter.lon=i[0],this.viewCenter.lat=i[1]}this.requestRedraw()},t.prototype.showHealpixGrid=function(t){this.displayHpxGrid=t,this.requestRedraw()},t.prototype.showSurvey=function(t){this.displaySurvey=t,this.requestRedraw()},t.prototype.showCatalog=function(t){this.displayCatalog=t,this.displayCatalog||(this.mustClearCatalog=!0),this.requestRedraw()},t.prototype.showReticle=function(t){this.displayReticle=t,this.mustRedrawReticle=!0,this.requestRedraw()},t.prototype.pointTo=function(t,e){if(t=parseFloat(t),e=parseFloat(e),!isNaN(t)&&!isNaN(e)){if(this.cooFrame==CooFrameEnum.J2000)this.viewCenter.lon=t,this.viewCenter.lat=e;else if(this.cooFrame==CooFrameEnum.GAL){var i=CooConversion.J2000ToGalactic([t,e]);this.viewCenter.lon=i[0],this.viewCenter.lat=i[1]}this.forceRedraw(),this.requestRedraw();var o=this;setTimeout(function(){o.refreshProgressiveCats()},1e3)}},t.prototype.makeUniqLayerName=function(t){if(!this.layerNameExists(t))return t;for(var e=1;;++e){var i=t+"_"+e;if(!this.layerNameExists(i))return i}},t.prototype.layerNameExists=function(t){for(var e=this.catalogs,i=0;e.length>i;i++)if(t==e[i].name)return!0;return!1},t.prototype.removeLayers=function(){this.catalogs=[],this.overlays=[],this.requestRedraw()},t.prototype.addCatalog=function(t){t.name=this.makeUniqLayerName(t.name),this.catalogs.push(t),"catalog"==t.type?t.setView(this):"progressivecat"==t.type&&t.init(this)},t.prototype.addOverlay=function(t){this.overlays.push(t),t.setView(this)},t.prototype.getObjectsInBBox=function(t,e,i,o){0>i&&(t+=i,i=-i),0>o&&(e+=o,o=-o);var r,s,a,n=[];if(this.catalogs)for(var h=0;this.catalogs.length>h;h++)if(r=this.catalogs[h],r.isShowing){s=r.getSources();for(var l=0;s.length>l;l++)a=s[l],a.isShowing&&a.x&&a.y&&a.x>=t&&t+i>=a.x&&a.y>=e&&e+o>=a.y&&n.push(a)}return n},t.prototype.updateObjectsLookup=function(){this.objLookup=[];var t,e,i,o,r;if(this.catalogs)for(var s=0;this.catalogs.length>s;s++)if(t=this.catalogs[s],t.isShowing){e=t.getSources();for(var a=0;e.length>a;a++)i=e[a],i.isShowing&&i.x&&i.y&&(o=i.x,r=i.y,this.objLookup[o]||(this.objLookup[o]=[]),this.objLookup[o][r]||(this.objLookup[o][r]=[]),this.objLookup[o][r].push(i))}},t.prototype.closestObjects=function(t,e,i){if(!this.objLookup)return null;for(var o,r,s=0;i>=s;s++){o=r=null;for(var a=-i;i>=a;a++)if(this.objLookup[t+a])for(var n=-i;i>=n;n++)if(this.objLookup[t+a][e+n])if(o){var h=a*a+n*n;r>h&&(r=h,o=this.objLookup[t+a][e+n])}else o=this.objLookup[t+a][e+n];if(o)return o}return null},t}(),/** @license CDS - Centre de Données astronomiques de Strasbourg , 2013*/
Aladin=function(){var t=function(e,i){HealpixCache.init();var o=this;if($.ajax({url:"http://aladin.u-strasbg.fr/java/nph-aladin.pl",data:{frame:"aladinLiteDic"},method:"GET",dataType:"jsonp",success:function(t){for(var e={},i=0;t.length>i;i++)e[t[i].id]=!0;for(var i=0;HpxImageSurvey.SURVEYS.length>i;i++)e[HpxImageSurvey.SURVEYS[i].id]||t.push(HpxImageSurvey.SURVEYS[i]);HpxImageSurvey.SURVEYS=t},error:function(){}}),void 0===i&&(i=this.getOptionsFromQueryString()),i=i||{},"zoom"in i){var r=i.zoom;delete i.zoom,i.fov=r}var s={};for(var a in t.DEFAULT_OPTIONS)s[a]=void 0!==i[a]?i[a]:t.DEFAULT_OPTIONS[a];for(var a in i)void 0===t.DEFAULT_OPTIONS[a]&&(s[a]=i[a]);this.options=s,this.aladinDiv=e,$(e).addClass("aladin-container");var n=CooFrameEnum.fromString(s.cooFrame,CooFrameEnum.J2000),h=n==CooFrameEnum.J2000,l=$('<div class="aladin-location">'+(s.showFrame?'<select class="aladin-frameChoice"><option '+(h?'selected="selected"':"")+">J2000</option><option "+(h?"":'selected="selected"')+">GAL</option></select>":"")+'<span class="aladin-location-text"></span></div>').appendTo(e),c=$('<div class="aladin-fov"></div>').appendTo(e);s.showZoomControl&&$('<div class="aladin-zoomControl"><a href="#" class="zoomPlus" title="Zoom in">+</a><a href="#" class="zoomMinus" title="Zoom out">&ndash;</a></div>').appendTo(e),s.showFullscreenControl&&$('<div class="aladin-fullscreenControl aladin-maximize" title="Full screen"></div>').appendTo(e),this.fullScreenBtn=$(e).find(".aladin-fullscreenControl"),this.fullScreenBtn.click(function(){o.toggleFullscreen()}),$("<div class='aladin-logo-container'><a href='http://aladin.u-strasbg.fr/AladinLite/' title='Powered by Aladin Lite' target='_blank'><div class='aladin-logo'></div></a></div>").appendTo(e),this.boxes=[];var u=new Location(l.find(".aladin-location-text"));if(this.view=new View(this,u,c,n,s.fov),s.showLayersControl){var d=$('<div class="aladin-layersControl-container" title="Manage layers"><div class="aladin-layersControl"></div></div>');d.appendTo(e);var p=$('<div class="aladin-box aladin-layerBox aladin-cb-list"></div>');p.appendTo(e),this.boxes.push(p),d.click(function(){return o.hideBoxes(),o.showLayerBox(),!1})}if(s.showGotoControl){var d=$('<div class="aladin-gotoControl-container" title="Go to position"><div class="aladin-gotoControl"></div></div>');d.appendTo(e);var f=$('<div class="aladin-box aladin-gotoBox"><a class="aladin-closeBtn">&times;</a><div style="clear: both;"></div><form class="aladin-target-form">Go to: <input type="text" placeholder="Object name/position" /></form></div>');f.appendTo(e),this.boxes.push(f);var v=f.find(".aladin-target-form input");v.on("paste keydown",function(){$(this).removeClass("aladin-unknownObject")}),d.click(function(){return o.hideBoxes(),v.val(""),v.removeClass("aladin-unknownObject"),f.show(),v.focus(),!1}),f.find(".aladin-closeBtn").click(function(){return o.hideBoxes(),!1})}if(s.showShareControl){var d=$('<div class="aladin-shareControl-container" title="Share current view"><div class="aladin-shareControl"></div></div>');d.appendTo(e);var g=$('<div class="aladin-box aladin-shareBox"><a class="aladin-closeBtn">&times;</a><div style="clear: both;"></div><b>Share</b><input type="text" class="aladin-shareInput" /></div>');g.appendTo(e),this.boxes.push(g),d.click(function(){return o.hideBoxes(),g.show(),!1}),g.find(".aladin-closeBtn").click(function(){return o.hideBoxes(),!1})}if(this.gotoObject(s.target),s.log){var m=i;m.version=t.VERSION,Logger.log("startup",m)}if(this.showReticle(s.showReticle),s.catalogUrls)for(var y=0,w=s.catalogUrls.length;w>y;y++)this.createCatalogFromVOTable(s.catalogUrls[y]);this.setImageSurvey(s.survey),this.view.showCatalog(s.showCatalog);var C=this;$(e).find(".aladin-frameChoice").change(function(){C.setFrame($(this).val())}),$("#projectionChoice").change(function(){C.setProjection($(this).val())}),$(e).find(".aladin-target-form").submit(function(){return C.gotoObject($(this).find("input").val(),function(){$(e).find(".aladin-target-form input").addClass("aladin-unknownObject")}),!1});var x=$(e).find(".zoomPlus");x.click(function(){return C.increaseZoom(),!1}),x.bind("mousedown",function(t){t.preventDefault()});var S=$(e).find(".zoomMinus");S.click(function(){return C.decreaseZoom(),!1}),S.bind("mousedown",function(t){t.preventDefault()}),s.fullScreen&&window.setTimeout(function(){o.toggleFullscreen()},1e3)};return t.VERSION="2014-09-17",t.JSONP_PROXY="http://alasky.u-strasbg.fr/cgi/JSONProxy",t.DEFAULT_OPTIONS={target:"0 +0",cooFrame:"J2000",survey:"P/DSS2/color",fov:60,showReticle:!0,showZoomControl:!0,showFullscreenControl:!0,showLayersControl:!0,showGotoControl:!0,showShareControl:!1,showCatalog:!0,showFrame:!0,fullScreen:!1,reticleColor:"rgb(178, 50, 178)",reticleSize:22,log:!0},t.prototype.toggleFullscreen=function(){this.fullScreenBtn.toggleClass("aladin-maximize aladin-restore");var t=this.fullScreenBtn.hasClass("aladin-restore");this.fullScreenBtn.attr("title",t?"Restore original size":"Full screen"),$(this.aladinDiv).toggleClass("aladin-fullscreen"),this.view.fixLayoutDimensions()},t.prototype.updateSurveysDropdownList=function(t){t=t.sort(function(t,e){return t.order?t.order&&t.order>e.order?1:-1:t.id>e.id});var e=$(this.aladinDiv).find(".aladin-surveySelection");e.empty();for(var i=0;t.length>i;i++){var o=this.view.imageSurvey.id==t[i].id;e.append($("<option />").attr("selected",o).val(t[i].id).text(t[i].name))}},t.prototype.getOptionsFromQueryString=function(){var t={},e=$.urlParam("target");e&&(t.target=e);var i=$.urlParam("frame");i&&CooFrameEnum[i]&&(t.frame=i);var o=$.urlParam("survey");o&&HpxImageSurvey.getSurveyInfoFromId(o)&&(t.survey=o);var r=$.urlParam("zoom");r&&r>0&&180>r&&(t.zoom=r);var s=$.urlParam("showReticle");s&&(t.showReticle="true"==s.toLowerCase());var a=$.urlParam("cooFrame");a&&(t.cooFrame=a);var n=$.urlParam("fullScreen");return void 0!==n&&(t.fullScreen=n),t},t.prototype.setZoom=function(t){this.view.setZoom(t)},t.prototype.setFoV=function(t){this.view.setZoom(t)},t.prototype.setFrame=function(t){t&&(t=t.toLowerCase(),0==t.indexOf("j2000")?this.view.changeFrame(CooFrameEnum.J2000):0==t.indexOf("gal")&&this.view.changeFrame(CooFrameEnum.GAL))},t.prototype.setProjection=function(t){if(t)switch(t=t.toLowerCase()){case"aitoff":this.view.changeProjection(ProjectionEnum.AITOFF);break;case"sinus":default:this.view.changeProjection(ProjectionEnum.SIN)}},t.prototype.gotoObject=function(t,e){var i=/[a-zA-Z]/.test(t);if(i){var o=this;Sesame.resolve(t,function(t){var e=t.Target.Resolver.jradeg,i=t.Target.Resolver.jdedeg;o.view.pointTo(e,i)},function(i){console&&(console.log("Could not resolve object name "+t),console.log(i)),e&&e()})}else{var r=new Coo;r.parse(t);var s=[r.lon,r.lat];this.view.cooFrame==CooFrameEnum.GAL&&(s=CooConversion.GalacticToJ2000(s)),this.view.pointTo(s[0],s[1])}},t.prototype.gotoPosition=function(t,e){var i;i=this.view.cooFrame==CooFrameEnum.GAL?CooConversion.GalacticToJ2000([t,e]):[t,e],this.view.pointTo(i[0],i[1])},t.prototype.gotoRaDec=function(t,e){this.view.pointTo(t,e)},t.prototype.showHealpixGrid=function(t){this.view.showHealpixGrid(t)},t.prototype.showSurvey=function(t){this.view.showSurvey(t)},t.prototype.showCatalog=function(t){this.view.showCatalog(t)},t.prototype.showReticle=function(t){this.view.showReticle(t),$("#displayReticle").attr("checked",t)},t.prototype.removeLayers=function(){this.view.removeLayers()},t.prototype.addCatalog=function(t){this.view.addCatalog(t)},t.prototype.addOverlay=function(t){this.view.addOverlay(t)},t.prototype.createImageSurvey=function(t,e,i,o,r,s){return new HpxImageSurvey(t,e,i,o,r,s)},t.prototype.getBaseImageLayer=function(){return this.view.imageSurvey},t.prototype.setImageSurvey=function(t,e){if(this.view.setImageSurvey(t,e),this.updateSurveysDropdownList(HpxImageSurvey.getAvailableSurveys()),this.options.log){var i=t;"string"!=typeof t&&(i=t.rootUrl),Logger.log("changeImageSurvey",i)}},t.prototype.setBaseImageLayer=t.prototype.setImageSurvey,t.prototype.getOverlayImageLayer=function(){return this.view.overlayImageSurvey},t.prototype.setOverlayImageLayer=function(t,e){this.view.setOverlayImageSurvey(t,e)},t.prototype.increaseZoom=function(t){t||(t=5),this.view.setZoomLevel(this.view.zoomLevel+t)},t.prototype.decreaseZoom=function(t){t||(t=5),this.view.setZoomLevel(this.view.zoomLevel-t)},t.prototype.createCatalog=function(t){return new cds.Catalog(t)},t.prototype.createProgressiveCatalog=function(t,e,i,o){return new ProgressiveCat(t,e,i,o)},t.prototype.createSource=function(t,e,i){return new cds.Source(t,e,i)},t.prototype.createMarker=function(t,e,i,o){return i=i||{},i.marker=!0,new cds.Source(t,e,o,i)},t.prototype.createOverlay=function(t){return new Overlay(t)},t.prototype.createFootprintsFromSTCS=function(t){for(var e=Overlay.parseSTCS(t),i=[],o=0,r=e.length;r>o;o++)i.push(new Footprint(e[o]));return i},t.prototype.createCatalogFromVOTable=function(t,e){var i=this,o=i.createCatalog(e);return cds.Catalog.parseVOTable(t,function(t){o.addSources(t)}),o},t.prototype.on=function(t,e){"select"===t?this.selectFunction=e:"objectClicked"==t?this.objClickedFunction=e:"objectHovered"==t&&(this.objHoveredFunction=e)},t.prototype.select=function(){this.fire("selectstart")},t.prototype.fire=function(t,e){"selectstart"===t?this.view.setMode(View.SELECT):"selectend"===t&&(this.view.setMode(View.PAN),this.selectFunction&&this.selectFunction(e))},t.prototype.hideBoxes=function(){if(this.boxes)for(var t=0;this.boxes.length>t;t++)this.boxes[t].hide()},t.prototype.updateCM=function(){},t.prototype.showLayerBox=function(){var t=this,e=$(this.aladinDiv).find(".aladin-layerBox");e.empty(),e.append('<a class="aladin-closeBtn">&times;</a><div style="clear: both;"></div><div class="aladin-label">Base image layer</div><select class="aladin-surveySelection"></select><div class="aladin-cmap">Color map:<div><select class="aladin-cmSelection"></select><button class="aladin-btn aladin-btn-small aladin-reverseCm" type="button">Reverse</button></div></div><div class="aladin-box-separator"></div><div class="aladin-label">Overlay layers</div>');for(var i=e.find(".aladin-cmap"),o=e.find(".aladin-cmSelection"),r=0;ColorMap.MAPS_NAMES.length>r;r++)o.append($("<option />").text(ColorMap.MAPS_NAMES[r]));o.val(t.getBaseImageLayer().getColorMap().map);for(var s=this.view.catalogs,a="<ul>",r=s.length-1;r>=0;r--){var n=s[r].name,h="";s[r].isShowing&&(h='checked="checked"');var l=s[r].getSources().length,c=l+" source"+(l>1?"s":"");a+='<li><div class="aladin-layerIcon" style="background: '+s[r].color+';"></div><input type="checkbox" '+h+' id="aladin_lite_'+n+'"></input><label for="aladin_lite_'+n+'" title="'+c+'">'+n+"</label></li>"}a+="</ul>",e.append(a),e.append('<div class="aladin-blank-separator"></div>');var h="";this.view.displayReticle&&(h='checked="checked"');var u=$('<input type="checkbox" '+h+' id="displayReticle" />');e.append(u).append('<label for="displayReticle">Reticle</label><br/>'),u.change(function(){t.showReticle($(this).is(":checked"))}),h="",this.view.displayHpxGrid&&(h='checked="checked"');var d=$('<input type="checkbox" '+h+' id="displayHpxGrid"/>');e.append(d).append('<label for="displayHpxGrid">HEALPix grid</label><br/>'),d.change(function(){t.showHealpixGrid($(this).is(":checked"))}),e.append('<div class="aladin-box-separator"></div><div class="aladin-label">Tools</div>');var p=$('<button class="aladin-btn" type="button">Export view as PNG</button>');e.append(p),p.click(function(){t.exportAsPNG()}),e.find(".aladin-closeBtn").click(function(){return t.hideBoxes(),!1}),this.updateSurveysDropdownList(HpxImageSurvey.getAvailableSurveys());var f=$(this.aladinDiv).find(".aladin-surveySelection");f.change(function(){var e=HpxImageSurvey.getAvailableSurveys()[$(this)[0].selectedIndex];t.setImageSurvey(e.id,function(){var e=t.getBaseImageLayer();e.useCors?(o.val(e.getColorMap().map),i.show(),p.show()):(i.hide(),p.hide())})}),i.find(".aladin-cmSelection").change(function(){var e=$(this).find(":selected").val();t.getBaseImageLayer().getColorMap().update(e)}),i.find(".aladin-reverseCm").click(function(){t.getBaseImageLayer().getColorMap().reverse()}),this.getBaseImageLayer().useCors?(i.show(),p.show()):(i.hide(),p.hide()),e.find(".aladin-reverseCm").parent().attr("disabled",!0),$(this.aladinDiv).find(".aladin-layerBox ul input").change(function(){var e=$(this).attr("id").substr(12),i=t.layerByName(e);$(this).is(":checked")?i.show():i.hide()}),e.show()},t.prototype.layerByName=function(t){for(var e=this.view.catalogs,i=0;this.view.catalogs.length>i;i++)if(t==e[i].name)return e[i];return null},t.prototype.exportAsPNG=function(){var t=this.view.getCanvasDataURL();window.open(t,"Aladin Lite snapshot")},t.prototype.setFOVRange=function(t,e){if(t>e){var i=t;t=e,e=i}this.view.minFOV=t,this.view.maxFOV=e},t.prototype.pix2world=function(t,e){var i,o=AladinUtils.viewToXy(t,e,this.view.width,this.view.height,this.view.largestDim,this.view.zoomFactor),r=this.view.projection.unproject(o.x,o.y);return i=this.view.cooFrame==CooFrameEnum.GAL?CooConversion.GalacticToJ2000([r.ra,r.dec]):[r.ra,r.dec]},t.prototype.world2pix=function(t,e){var i;if(this.view.cooFrame==CooFrameEnum.GAL){var o=CooConversion.J2000ToGalactic([t,e]);i=this.view.projection.project(o[0],o[1])}else i=this.view.projection.project(t,e);if(i){var r=AladinUtils.xyToView(i.X,i.Y,this.view.width,this.view.height,this.view.largestDim,this.view.zoomFactor);return[r.vx,r.vy]}return null},t.prototype.getFovCorners=function(t){(!t||1>t)&&(t=1);for(var e,i,o,r,s=[],a=0;4>a;a++){e=0==a||3==a?0:this.view.width-1,i=2>a?0:this.view.height-1,o=2>a?this.view.width-1:0,r=1==a||2==a?this.view.height-1:0;for(var n=0;t>n;n++)s.push(this.pix2world(e+n/t*(o-e),i+n/t*(r-i)))}return s},t.prototype.getFov=function(){var t=this.view.fov,e=this.getSize(),i=e[1]/e[0]*t;return t=Math.min(t,180),i=Math.min(i,180),[t,i]},t.prototype.getSize=function(){return[this.view.width,this.view.height]},t.prototype.getParentDiv=function(){return $(this.aladinDiv)},t}(),A.aladin=function(t,e){return new Aladin($(t)[0],e)},A.imageLayer=function(t,e,i,o){return new HpxImageSurvey(t,e,i,null,null,o)},A.source=function(t,e,i){return new cds.Source(t,e,i)},A.polygon=function(t){var e=t.length;return e>0&&(t[0][0]!=t[e-1][0]||t[0][1]!=t[e-1][1])&&t.push([t[0][0],t[0][1]]),new Footprint(t)},A.graphicOverlay=function(t){return new Overlay(t)},$&&($.aladin=A.aladin);
console.log('=============== >  aladin.min.js ');

/*
 * jsTree 1.0-rc1
 * http://jstree.com/
 *
 * Copyright (c) 2010 Ivan Bozhanov (vakata.com)
 *
 * Dual licensed under the MIT and GPL licenses (same as jQuery):
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 * $Date: 2010-07-01 10:51:11 +0300 (четв, 01 юли 2010) $
 * $Revision: 191 $
 */

/*jslint browser: true, onevar: true, undef: true, bitwise: true, strict: true */
/*global window : false, clearInterval: false, clearTimeout: false, document: false, setInterval: false, setTimeout: false, jQuery: false, navigator: false, XSLTProcessor: false, DOMParser: false, XMLSerializer: false*/

"use strict";
// Common functions not related to jsTree 
// decided to move them to a `vakata` "namespace"
(function ($) {
	$.vakata = {};
	// CSS related functions
	$.vakata.css = {
		get_css : function(rule_name, delete_flag, sheet) {
			rule_name = rule_name.toLowerCase();
			var css_rules = sheet.cssRules || sheet.rules,
				j = 0;
			do {
				if(css_rules.length && j > css_rules.length + 5) { return false; }
				if(css_rules[j].selectorText && css_rules[j].selectorText.toLowerCase() == rule_name) {
					if(delete_flag === true) {
						if(sheet.removeRule) { sheet.removeRule(j); }
						if(sheet.deleteRule) { sheet.deleteRule(j); }
						return true;
					}
					else { return css_rules[j]; }
				}
			}
			while (css_rules[++j]);
			return false;
		},
		add_css : function(rule_name, sheet) {
			if($.jstree.css.get_css(rule_name, false, sheet)) { return false; }
			if(sheet.insertRule) { sheet.insertRule(rule_name + ' { }', 0); } else { sheet.addRule(rule_name, null, 0); }
			return $.vakata.css.get_css(rule_name);
		},
		remove_css : function(rule_name, sheet) { 
			return $.vakata.css.get_css(rule_name, true, sheet); 
		},
		add_sheet : function(opts) {
			var tmp;
			if(opts.str) {
				tmp = document.createElement("style");
				tmp.setAttribute('type',"text/css");
				if(tmp.styleSheet) {
					document.getElementsByTagName("head")[0].appendChild(tmp);
					tmp.styleSheet.cssText = opts.str;
				}
				else {
					tmp.appendChild(document.createTextNode(opts.str));
					document.getElementsByTagName("head")[0].appendChild(tmp);
				}
				return tmp.sheet || tmp.styleSheet;
			}
			if(opts.url) {
				if(document.createStyleSheet) {
					try { tmp = document.createStyleSheet(opts.url); } catch (e) { }
				}
				else {
					tmp			= document.createElement('link');
					tmp.rel		= 'stylesheet';
					tmp.type	= 'text/css';
					tmp.media	= "all";
					tmp.href	= opts.url;
					document.getElementsByTagName("head")[0].appendChild(tmp);
					return tmp.styleSheet;
				}
			}
		}
	};
})(jQuery);

/* 
 * jsTree core 1.0
 */
(function ($) {
	// private variables 
	var instances = [],			// instance array (used by $.jstree.reference/create/focused)
		focused_instance = -1,	// the index in the instance array of the currently focused instance
		plugins = {},			// list of included plugins
		prepared_move = {},		// for the move plugin
		is_ie6 = false;

	// jQuery plugin wrapper (thanks to jquery UI widget function)
	$.fn.jstree = function (settings) {
		var isMethodCall = (typeof settings == 'string'), // is this a method call like $().jstree("open_node")
			args = Array.prototype.slice.call(arguments, 1), 
			returnValue = this;

		// extend settings and allow for multiple hashes and metadata
		if(!isMethodCall && $.meta) { args.push($.metadata.get(this).jstree); }
		settings = !isMethodCall && args.length ? $.extend.apply(null, [true, settings].concat(args)) : settings;
		// block calls to "private" methods
		if(isMethodCall && settings.substring(0, 1) == '_') { return returnValue; }

		// if a method call execute the method on all selected instances
		if(isMethodCall) {
			this.each(function() {
				var instance = instances[$.data(this, "jstree-instance-id")],
					methodValue = (instance && $.isFunction(instance[settings])) ? instance[settings].apply(instance, args) : instance;
					if(typeof methodValue !== "undefined" && (settings.indexOf("is_" === 0) || (methodValue !== true && methodValue !== false))) { returnValue = methodValue; return false; }
			});
		}
		else {
			this.each(function() {
				var instance_id = $.data(this, "jstree-instance-id"),
					s = false;
				// if an instance already exists, destroy it first
				if(typeof instance_id !== "undefined" && instances[instance_id]) { instances[instance_id].destroy(); }
				// push a new empty object to the instances array
				instance_id = parseInt(instances.push({}),10) - 1;
				// store the jstree instance id to the container element
				$.data(this, "jstree-instance-id", instance_id);
				// clean up all plugins
				if(!settings) { settings = {}; }
				settings.plugins = $.isArray(settings.plugins) ? settings.plugins : $.jstree.defaults.plugins;
				if($.inArray("core", settings.plugins) === -1) { settings.plugins.unshift("core"); }
				
				// only unique plugins (NOT WORKING)
				// settings.plugins = settings.plugins.sort().join(",,").replace(/(,|^)([^,]+)(,,\2)+(,|$)/g,"$1$2$4").replace(/,,+/g,",").replace(/,$/,"").split(",");

				// extend defaults with passed data
				s = $.extend(true, {}, $.jstree.defaults, settings);
				s.plugins = settings.plugins;
				$.each(plugins, function (i, val) { if($.inArray(i, s.plugins) === -1) { s[i] = null; delete s[i]; } });
				// push the new object to the instances array (at the same time set the default classes to the container) and init
				instances[instance_id] = new $.jstree._instance(instance_id, $(this).addClass("jstree jstree-" + instance_id), s); 
				// init all activated plugins for this instance
				$.each(instances[instance_id]._get_settings().plugins, function (i, val) { instances[instance_id].data[val] = {}; });
				$.each(instances[instance_id]._get_settings().plugins, function (i, val) { if(plugins[val]) { plugins[val].__init.apply(instances[instance_id]); } });
				// initialize the instance
				instances[instance_id].init();
			});
		}
		// return the jquery selection (or if it was a method call that returned a value - the returned value)
		return returnValue;
	};
	// object to store exposed functions and objects
	$.jstree = {
		defaults : {
			plugins : []
		},
		_focused : function () { return instances[focused_instance] || null; },
		_reference : function (needle) { 
			// get by instance id
			if(instances[needle]) { return instances[needle]; }
			// get by DOM (if still no luck - return null
			var o = $(needle); 
			if(!o.length && typeof needle === "string") { o = $("#" + needle); }
			if(!o.length) { return null; }
			return instances[o.closest(".jstree").data("jstree-instance-id")] || null; 
		},
		_instance : function (index, container, settings) { 
			// for plugins to store data in
			this.data = { core : {} };
			this.get_settings	= function () { return $.extend(true, {}, settings); };
			this._get_settings	= function () { return settings; };
			this.get_index		= function () { return index; };
			this.get_container	= function () { return container; };
			this._set_settings	= function (s) { 
				settings = $.extend(true, {}, settings, s);
			};
		},
		_fn : { },
		plugin : function (pname, pdata) {
			pdata = $.extend({}, {
				__init		: $.noop, 
				__destroy	: $.noop,
				_fn			: {},
				defaults	: false
			}, pdata);
			plugins[pname] = pdata;

			$.jstree.defaults[pname] = pdata.defaults;
			$.each(pdata._fn, function (i, val) {
				val.plugin		= pname;
				val.old			= $.jstree._fn[i];
				$.jstree._fn[i] = function () {
					var rslt,
						func = val,
						args = Array.prototype.slice.call(arguments),
						evnt = new $.Event("before.jstree"),
						rlbk = false;

					// Check if function belongs to the included plugins of this instance
					do {
						if(func && func.plugin && $.inArray(func.plugin, this._get_settings().plugins) !== -1) { break; }
						func = func.old;
					} while(func);
					if(!func) { return; }

					// a chance to stop execution (or change arguments): 
					// * just bind to jstree.before
					// * check the additional data object (func property)
					// * call event.stopImmediatePropagation()
					// * return false (or an array of arguments)
					rslt = this.get_container().triggerHandler(evnt, { "func" : i, "inst" : this, "args" : args });
					if(rslt === false) { return; }
					if(typeof rslt !== "undefined") { args = rslt; }

					// context and function to trigger events, then finally call the function
					if(i.indexOf("_") === 0) {
						rslt = func.apply(this, args);
					}
					else {
						rslt = func.apply(
							$.extend({}, this, { 
								__callback : function (data) { 
									this.get_container().triggerHandler( i + '.jstree', { "inst" : this, "args" : args, "rslt" : data, "rlbk" : rlbk });
								},
								__rollback : function () { 
									rlbk = this.get_rollback();
									return rlbk;
								},
								__call_old : function (replace_arguments) {
									return func.old.apply(this, (replace_arguments ? Array.prototype.slice.call(arguments, 1) : args ) );
								}
							}), args);
					}

					// return the result
					return rslt;
				};
				$.jstree._fn[i].old = val.old;
				$.jstree._fn[i].plugin = pname;
			});
		},
		rollback : function (rb) {
			if(rb) {
				if(!$.isArray(rb)) { rb = [ rb ]; }
				$.each(rb, function (i, val) {
					instances[val.i].set_rollback(val.h, val.d);
				});
			}
		}
	};
	// set the prototype for all instances
	$.jstree._fn = $.jstree._instance.prototype = {};

	// css functions - used internally

	// load the css when DOM is ready
	$(function() {
		// code is copied form jQuery ($.browser is deprecated + there is a bug in IE)
		var u = navigator.userAgent.toLowerCase(),
			v = (u.match( /.+?(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1],
			css_string = '' + 
				'.jstree ul, .jstree li { display:block; margin:0 0 0 0; padding:0 0 0 0; list-style-type:none; } ' + 
				'.jstree li { display:block; min-height:18px; line-height:18px; white-space:nowrap; margin-left:18px; } ' + 
				'.jstree-rtl li { margin-left:0; margin-right:18px; } ' + 
				'.jstree > ul > li { margin-left:0px; } ' + 
				'.jstree-rtl > ul > li { margin-right:0px; } ' + 
				'.jstree ins { display:inline-block; text-decoration:none; width:18px; height:18px; margin:0 0 0 0; padding:0; } ' + 
				'.jstree a { display:inline-block; line-height:16px; height:16px; color:black; white-space:nowrap; text-decoration:none; padding:1px 2px; margin:0; } ' + 
				'.jstree a:focus { outline: none; } ' + 
				'.jstree a > ins { height:16px; width:16px; } ' + 
				'.jstree a > .jstree-icon { margin-right:3px; } ' + 
				'.jstree-rtl a > .jstree-icon { margin-left:3px; margin-right:0; } ' + 
				'li.jstree-open > ul { display:block; } ' + 
				'li.jstree-closed > ul { display:none; } ';
		// Correct IE 6 (does not support the > CSS selector)
		if(/msie/.test(u) && parseInt(v, 10) == 6) { 
			is_ie6 = true;
			css_string += '' + 
				'.jstree li { height:18px; margin-left:0; margin-right:0; } ' + 
				'.jstree li li { margin-left:18px; } ' + 
				'.jstree-rtl li li { margin-left:0px; margin-right:18px; } ' + 
				'li.jstree-open ul { display:block; } ' + 
				'li.jstree-closed ul { display:none !important; } ' + 
				'.jstree li a { display:inline; border-width:0 !important; padding:0px 2px !important; } ' + 
				'.jstree li a ins { height:16px; width:16px; margin-right:3px; } ' + 
				'.jstree-rtl li a ins { margin-right:0px; margin-left:3px; } ';
		}
		// Correct IE 7 (shifts anchor nodes onhover)
		if(/msie/.test(u) && parseInt(v, 10) == 7) { 
			css_string += '.jstree li a { border-width:0 !important; padding:0px 2px !important; } ';
		}
		$.vakata.css.add_sheet({ str : css_string });
	});

	// core functions (open, close, create, update, delete)
	$.jstree.plugin("core", {
		__init : function () {
			this.data.core.to_open = $.map($.makeArray(this.get_settings().core.initially_open), function (n) { return "#" + n.toString().replace(/^#/,"").replace('\\/','/').replace('/','\\/'); });
		},
		defaults : { 
			html_titles	: false,
			animation	: 500,
			initially_open : [],
			rtl			: false,
			strings		: {
				loading		: "Loading ...",
				new_node	: "New node"
			}
		},
		_fn : { 
			init	: function () { 
				this.set_focus(); 
				if(this._get_settings().core.rtl) {
					this.get_container().addClass("jstree-rtl").css("direction", "rtl");
				}
				this.get_container().html("<ul><li class='jstree-last jstree-leaf'><ins>&#160;</ins><a class='jstree-loading' href='#'><ins class='jstree-icon'>&#160;</ins>" + this._get_settings().core.strings.loading + "</a></li></ul>");
				this.data.core.li_height = this.get_container().find("ul li.jstree-closed, ul li.jstree-leaf").eq(0).height() || 18;

				this.get_container()
					.delegate("li > ins", "click.jstree", $.proxy(function (event) {
							var trgt = $(event.target);
							if(trgt.is("ins") && event.pageY - trgt.offset().top < this.data.core.li_height) { this.toggle_node(trgt); }
						}, this))
					.bind("mousedown.jstree", $.proxy(function () { 
							this.set_focus(); // This used to be setTimeout(set_focus,0) - why?
						}, this))
					.bind("dblclick.jstree", function (event) { 
						var sel;
						if(document.selection && document.selection.empty) { document.selection.empty(); }
						else {
							if(window.getSelection) {
								sel = window.getSelection();
								try { 
									sel.removeAllRanges();
									sel.collapse();
								} catch (err) { }
							}
						}
					});
				this.__callback();
				this.load_node(-1, function () { this.loaded(); this.reopen(); });
			},
			destroy	: function () { 
				var i,
					n = this.get_index(),
					s = this._get_settings(),
					_this = this;

				$.each(s.plugins, function (i, val) {
					try { plugins[val].__destroy.apply(_this); } catch(err) { }
				});
				this.__callback();
				// set focus to another instance if this one is focused
				if(this.is_focused()) { 
					for(i in instances) { 
						if(instances.hasOwnProperty(i) && i != n) { 
							instances[i].set_focus(); 
							break; 
						} 
					}
				}
				// if no other instance found
				if(n === focused_instance) { focused_instance = -1; }
				// remove all traces of jstree in the DOM (only the ones set using jstree*) and cleans all events
				this.get_container()
					.unbind(".jstree")
					.undelegate(".jstree")
					.removeData("jstree-instance-id")
					.find("[class^='jstree']")
						.andSelf()
						.attr("class", function () { return this.className.replace(/jstree[^ ]*|$/ig,''); });
				// remove the actual data
				instances[n] = null;
				delete instances[n];
			},
			save_opened : function () {
				var _this = this;
				this.data.core.to_open = [];
				this.get_container().find(".jstree-open").each(function () { 
					_this.data.core.to_open.push("#" + this.id.toString().replace(/^#/,"").replace('\\/','/').replace('/','\\/')); 
				});
				this.__callback(_this.data.core.to_open);
			},
			reopen : function (is_callback) {
				var _this = this,
					done = true,
					current = [],
					remaining = [];
				if(!is_callback) { this.data.core.reopen = false; this.data.core.refreshing = true; }
				if(this.data.core.to_open.length) {
					$.each(this.data.core.to_open, function (i, val) {
						if(val == "#") { return true; }
						if($(val).length && $(val).is(".jstree-closed")) { current.push(val); }
						else { remaining.push(val); }
					});
					if(current.length) {
						this.data.core.to_open = remaining;
						$.each(current, function (i, val) { 
							_this.open_node(val, function () { _this.reopen(true); }, true); 
						});
						done = false;
					}
				}
				if(done) { 
					// TODO: find a more elegant approach to syncronizing returning requests
					if(this.data.core.reopen) { clearTimeout(this.data.core.reopen); }
					this.data.core.reopen = setTimeout(function () { _this.__callback({}, _this); }, 50);
					this.data.core.refreshing = false;
				}
			},
			refresh : function (obj) {
				var _this = this;
				this.save_opened();
				if(!obj) { obj = -1; }
				obj = this._get_node(obj);
				if(!obj) { obj = -1; }
				if(obj !== -1) { obj.children("UL").remove(); }
				this.load_node(obj, function () { _this.__callback({ "obj" : obj}); _this.reopen(); });
			},
			// Dummy function to fire after the first load (so that there is a jstree.loaded event)
			loaded	: function () { 
				this.__callback(); 
			},
			// deal with focus
			set_focus	: function () { 
				var f = $.jstree._focused();
				if(f && f !== this) {
					f.get_container().removeClass("jstree-focused"); 
				}
				if(f !== this) {
					this.get_container().addClass("jstree-focused"); 
					focused_instance = this.get_index(); 
				}
				this.__callback();
			},
			is_focused	: function () { 
				return focused_instance == this.get_index(); 
			},

			// traverse
			_get_node		: function (obj) { 
				var $obj = $(obj, this.get_container()); 
				if($obj.is(".jstree") || obj == -1) { return -1; } 
				$obj = $obj.closest("li", this.get_container()); 
				return $obj.length ? $obj : false; 
			},
			_get_next		: function (obj, strict) {
				obj = this._get_node(obj);
				if(obj === -1) { return this.get_container().find("> ul > li:first-child"); }
				if(!obj.length) { return false; }
				if(strict) { return (obj.nextAll("li").size() > 0) ? obj.nextAll("li:eq(0)") : false; }

				if(obj.hasClass("jstree-open")) { return obj.find("li:eq(0)"); }
				else if(obj.nextAll("li").size() > 0) { return obj.nextAll("li:eq(0)"); }
				else { return obj.parentsUntil(".jstree","li").next("li").eq(0); }
			},
			_get_prev		: function (obj, strict) {
				obj = this._get_node(obj);
				if(obj === -1) { return this.get_container().find("> ul > li:last-child"); }
				if(!obj.length) { return false; }
				if(strict) { return (obj.prevAll("li").length > 0) ? obj.prevAll("li:eq(0)") : false; }

				if(obj.prev("li").length) {
					obj = obj.prev("li").eq(0);
					while(obj.hasClass("jstree-open")) { obj = obj.children("ul:eq(0)").children("li:last"); }
					return obj;
				}
				else { var o = obj.parentsUntil(".jstree","li:eq(0)"); return o.length ? o : false; }
			},
			_get_parent		: function (obj) {
				obj = this._get_node(obj);
				if(obj == -1 || !obj.length) { return false; }
				var o = obj.parentsUntil(".jstree", "li:eq(0)");
				return o.length ? o : -1;
			},
			_get_children	: function (obj) {
				obj = this._get_node(obj);
				if(obj === -1) { return this.get_container().children("ul:eq(0)").children("li"); }
				if(!obj.length) { return false; }
				return obj.children("ul:eq(0)").children("li");
			},
			get_path		: function (obj, id_mode) {
				var p = [],
					_this = this;
				obj = this._get_node(obj);
				if(obj === -1 || !obj || !obj.length) { return false; }
				obj.parentsUntil(".jstree", "li").each(function () {
					p.push( id_mode ? this.id : _this.get_text(this) );
				});
				p.reverse();
				p.push( id_mode ? obj.attr("id") : this.get_text(obj) );
				return p;
			},

			is_open		: function (obj) { obj = this._get_node(obj); return obj && obj !== -1 && obj.hasClass("jstree-open"); },
			is_closed	: function (obj) { obj = this._get_node(obj); return obj && obj !== -1 && obj.hasClass("jstree-closed"); },
			is_leaf		: function (obj) { obj = this._get_node(obj); return obj && obj !== -1 && obj.hasClass("jstree-leaf"); },
			// open/close
			open_node	: function (obj, callback, skip_animation) {
				obj = this._get_node(obj);
				if(!obj.length) { return false; }
				if(!obj.hasClass("jstree-closed")) { if(callback) { callback.call(); } return false; }
				var s = skip_animation || is_ie6 ? 0 : this._get_settings().core.animation,
					t = this;
				if(!this._is_loaded(obj)) {
					obj.children("a").addClass("jstree-loading");
					this.load_node(obj, function () { t.open_node(obj, callback, skip_animation); }, callback);
				}
				else {
					if(s) { obj.children("ul").css("display","none"); }
					obj.removeClass("jstree-closed").addClass("jstree-open").children("a").removeClass("jstree-loading");
					if(s) { obj.children("ul").stop(true).slideDown(s, function () { this.style.display = ""; }); }
					this.__callback({ "obj" : obj });
					if(callback) { callback.call(); }
				}
			},
			close_node	: function (obj, skip_animation) {
				obj = this._get_node(obj);
				var s = skip_animation || is_ie6 ? 0 : this._get_settings().core.animation;
				if(!obj.length || !obj.hasClass("jstree-open")) { return false; }
				if(s) { obj.children("ul").attr("style","display:block !important"); }
				obj.removeClass("jstree-open").addClass("jstree-closed");
				if(s) { obj.children("ul").stop(true).slideUp(s, function () { this.style.display = ""; }); }
				this.__callback({ "obj" : obj });
			},
			toggle_node	: function (obj) {
				obj = this._get_node(obj);
				if(obj.hasClass("jstree-closed")) { return this.open_node(obj); }
				if(obj.hasClass("jstree-open")) { return this.close_node(obj); }
			},
			open_all	: function (obj, original_obj) {
				obj = obj ? this._get_node(obj) : this.get_container();
				if(!obj || obj === -1) { obj = this.get_container(); }
				if(original_obj) { 
					obj = obj.find("li.jstree-closed");
				}
				else {
					original_obj = obj;
					if(obj.is(".jstree-closed")) { obj = obj.find("li.jstree-closed").andSelf(); }
					else { obj = obj.find("li.jstree-closed"); }
				}
				var _this = this;
				obj.each(function () { 
					var __this = this; 
					if(!_this._is_loaded(this)) { _this.open_node(this, function() { _this.open_all(__this, original_obj); }, true); }
					else { _this.open_node(this, false, true); }
				});
				// so that callback is fired AFTER all nodes are open
				if(original_obj.find('li.jstree-closed').length === 0) { this.__callback({ "obj" : original_obj }); }
			},
			close_all	: function (obj) {
				var _this = this;
				obj = obj ? this._get_node(obj) : this.get_container();
				if(!obj || obj === -1) { obj = this.get_container(); }
				obj.find("li.jstree-open").andSelf().each(function () { _this.close_node(this); });
				this.__callback({ "obj" : obj });
			},
			clean_node	: function (obj) {
				obj = obj && obj != -1 ? $(obj) : this.get_container();
				obj = obj.is("li") ? obj.find("li").andSelf() : obj.find("li");
				obj.removeClass("jstree-last")
					.filter("li:last-child").addClass("jstree-last").end()
					.filter(":has(li)")
						.not(".jstree-open").removeClass("jstree-leaf").addClass("jstree-closed");
				obj.not(".jstree-open, .jstree-closed").addClass("jstree-leaf").children("ul").remove();
				this.__callback({ "obj" : obj });
			},
			// rollback
			get_rollback : function () { 
				this.__callback();
				return { i : this.get_index(), h : this.get_container().children("ul").clone(true), d : this.data }; 
			},
			set_rollback : function (html, data) {
				this.get_container().empty().append(html);
				this.data = data;
				this.__callback();
			},
			// Dummy functions to be overwritten by any datastore plugin included
			load_node	: function (obj, s_call, e_call) { this.__callback({ "obj" : obj }); },
			_is_loaded	: function (obj) { return true; },

			// Basic operations: create
			create_node	: function (obj, position, js, callback, is_loaded) {
				obj = this._get_node(obj);
				position = typeof position === "undefined" ? "last" : position;
				var d = $("<li>"),
					s = this._get_settings().core,
					tmp;

				if(obj !== -1 && !obj.length) { return false; }
				if(!is_loaded && !this._is_loaded(obj)) { this.load_node(obj, function () { this.create_node(obj, position, js, callback, true); }); return false; }

				this.__rollback();

				if(typeof js === "string") { js = { "data" : js }; }
				if(!js) { js = {}; }
				if(js.attr) { d.attr(js.attr); }
				if(js.state) { d.addClass("jstree-" + js.state); }
				if(!js.data) { js.data = s.strings.new_node; }
				if(!$.isArray(js.data)) { tmp = js.data; js.data = []; js.data.push(tmp); }
				$.each(js.data, function (i, m) {
					tmp = $("<a>");
					if($.isFunction(m)) { m = m.call(this, js); }
					if(typeof m == "string") { tmp.attr('href','#')[ s.html_titles ? "html" : "text" ](m); }
					else {
						if(!m.attr) { m.attr = {}; }
						if(!m.attr.href) { m.attr.href = '#'; }
						tmp.attr(m.attr)[ s.html_titles ? "html" : "text" ](m.title);
						if(m.language) { tmp.addClass(m.language); }
					}
					tmp.prepend("<ins class='jstree-icon'>&#160;</ins>");
					if(m.icon) { 
						if(m.icon.indexOf("/") === -1) { tmp.children("ins").addClass(m.icon); }
						else { tmp.children("ins").css("background","url('" + m.icon + "') center center no-repeat"); }
					}
					d.append(tmp);
				});
				d.prepend("<ins class='jstree-icon'>&#160;</ins>");
				if(obj === -1) {
					obj = this.get_container();
					if(position === "before") { position = "first"; }
					if(position === "after") { position = "last"; }
				}
				switch(position) {
					case "before": obj.before(d); tmp = this._get_parent(obj); break;
					case "after" : obj.after(d);  tmp = this._get_parent(obj); break;
					case "inside":
					case "first" :
						if(!obj.children("ul").length) { obj.append("<ul>"); }
						obj.children("ul").prepend(d);
						tmp = obj;
						break;
					case "last":
						if(!obj.children("ul").length) { obj.append("<ul>"); }
						obj.children("ul").append(d);
						tmp = obj;
						break;
					default:
						if(!obj.children("ul").length) { obj.append("<ul>"); }
						if(!position) { position = 0; }
						tmp = obj.children("ul").children("li").eq(position);
						if(tmp.length) { tmp.before(d); }
						else { obj.children("ul").append(d); }
						tmp = obj;
						break;
				}
				if(tmp === -1 || tmp.get(0) === this.get_container().get(0)) { tmp = -1; }
				this.clean_node(tmp);
				this.__callback({ "obj" : d, "parent" : tmp });
				if(callback) { callback.call(this, d); }
				return d;
			},
			// Basic operations: rename (deal with text)
			get_text	: function (obj) {
				obj = this._get_node(obj);
				if(!obj.length) { return false; }
				var s = this._get_settings().core.html_titles;
				obj = obj.children("a:eq(0)");
				if(s) {
					obj = obj.clone();
					obj.children("INS").remove();
					return obj.html();
				}
				else {
					obj = obj.contents().filter(function() { return this.nodeType == 3; })[0];
					return obj.nodeValue;
				}
			},
			set_text	: function (obj, val) {
				obj = this._get_node(obj);
				if(!obj.length) { return false; }
				obj = obj.children("a:eq(0)");
				if(this._get_settings().core.html_titles) {
					var tmp = obj.children("INS").clone();
					obj.html(val).prepend(tmp);
					this.__callback({ "obj" : obj, "name" : val });
					return true;
				}
				else {
					obj = obj.contents().filter(function() { return this.nodeType == 3; })[0];
					this.__callback({ "obj" : obj, "name" : val });
					return (obj.nodeValue = val);
				}
			},
			rename_node : function (obj, val) {
				obj = this._get_node(obj);
				this.__rollback();
				if(obj && obj.length && this.set_text.apply(this, Array.prototype.slice.call(arguments))) { this.__callback({ "obj" : obj, "name" : val }); }
			},
			// Basic operations: deleting nodes
			delete_node : function (obj) {
				obj = this._get_node(obj);
				if(!obj.length) { return false; }
				this.__rollback();
				var p = this._get_parent(obj), prev = this._get_prev(obj);
				obj = obj.remove();
				if(p !== -1 && p.find("> ul > li").length === 0) {
					p.removeClass("jstree-open jstree-closed").addClass("jstree-leaf");
				}
				this.clean_node(p);
				this.__callback({ "obj" : obj, "prev" : prev });
				return obj;
			},
			prepare_move : function (o, r, pos, cb, is_cb) {
				var p = {};

				p.ot = $.jstree._reference(p.o) || this;
				p.o = p.ot._get_node(o);
				p.r = r === - 1 ? -1 : this._get_node(r);
				p.p = (typeof p === "undefined") ? "last" : pos; // TODO: move to a setting
				if(!is_cb && prepared_move.o && prepared_move.o[0] === p.o[0] && prepared_move.r[0] === p.r[0] && prepared_move.p === p.p) {
					this.__callback(prepared_move);
					if(cb) { cb.call(this, prepared_move); }
					return;
				}
				p.ot = $.jstree._reference(p.o) || this;
				p.rt = r === -1 ? p.ot : $.jstree._reference(p.r) || this;
				if(p.r === -1) {
					p.cr = -1;
					switch(p.p) {
						case "first":
						case "before":
						case "inside":
							p.cp = 0; 
							break;
						case "after":
						case "last":
							p.cp = p.rt.get_container().find(" > ul > li").length; 
							break;
						default:
							p.cp = p.p;
							break;
					}
				}
				else {
					if(!/^(before|after)$/.test(p.p) && !this._is_loaded(p.r)) {
						return this.load_node(p.r, function () { this.prepare_move(o, r, pos, cb, true); });
					}
					switch(p.p) {
						case "before":
							p.cp = p.r.index();
							p.cr = p.rt._get_parent(p.r);
							break;
						case "after":
							p.cp = p.r.index() + 1;
							p.cr = p.rt._get_parent(p.r);
							break;
						case "inside":
						case "first":
							p.cp = 0;
							p.cr = p.r;
							break;
						case "last":
							p.cp = p.r.find(" > ul > li").length; 
							p.cr = p.r;
							break;
						default: 
							p.cp = p.p;
							p.cr = p.r;
							break;
					}
				}
				p.np = p.cr == -1 ? p.rt.get_container() : p.cr;
				p.op = p.ot._get_parent(p.o);
				p.or = p.np.find(" > ul > li:nth-child(" + (p.cp + 1) + ")");

				prepared_move = p;
				this.__callback(prepared_move);
				if(cb) { cb.call(this, prepared_move); }
			},
			check_move : function () {
				var obj = prepared_move, ret = true;
				if(obj.or[0] === obj.o[0]) { return false; }
				obj.o.each(function () { 
					if(obj.r.parentsUntil(".jstree").andSelf().filter("li").index(this) !== -1) { ret = false; return false; }
				});
				return ret;
			},
			move_node : function (obj, ref, position, is_copy, is_prepared, skip_check) {
				if(!is_prepared) { 
					return this.prepare_move(obj, ref, position, function (p) {
						this.move_node(p, false, false, is_copy, true, skip_check);
					});
				}
				if(!skip_check && !this.check_move()) { return false; }

				this.__rollback();
				var o = false;
				if(is_copy) {
					o = obj.o.clone();
					o.find("*[id]").andSelf().each(function () {
						if(this.id) { this.id = "copy_" + this.id; }
					});
				}
				else { o = obj.o; }

				if(obj.or.length) { obj.or.before(o); }
				else { 
					if(!obj.np.children("ul").length) { $("<ul>").appendTo(obj.np); }
					obj.np.children("ul:eq(0)").append(o); 
				}

				try { 
					obj.ot.clean_node(obj.op);
					obj.rt.clean_node(obj.np);
					if(!obj.op.find("> ul > li").length) {
						obj.op.removeClass("jstree-open jstree-closed").addClass("jstree-leaf").children("ul").remove();
					}
				} catch (e) { }

				if(is_copy) { 
					prepared_move.cy = true;
					prepared_move.oc = o; 
				}
				this.__callback(prepared_move);
				return prepared_move;
			},
			_get_move : function () { return prepared_move; }
		}
	});
})(jQuery);
//*/

/* 
 * jsTree ui plugin 1.0
 * This plugins handles selecting/deselecting/hovering/dehovering nodes
 */
(function ($) {
	$.jstree.plugin("ui", {
		__init : function () { 
			this.data.ui.selected = $(); 
			this.data.ui.last_selected = false; 
			this.data.ui.hovered = null;
			this.data.ui.to_select = this.get_settings().ui.initially_select;

			this.get_container()
				.delegate("a", "click.jstree", $.proxy(function (event) {
						event.preventDefault();
						this.select_node(event.currentTarget, true, event);
					}, this))
				.delegate("a", "mouseenter.jstree", $.proxy(function (event) {
						this.hover_node(event.target);
					}, this))
				.delegate("a", "mouseleave.jstree", $.proxy(function (event) {
						this.dehover_node(event.target);
					}, this))
				.bind("reopen.jstree", $.proxy(function () { 
						this.reselect();
					}, this))
				.bind("get_rollback.jstree", $.proxy(function () { 
						this.dehover_node();
						this.save_selected();
					}, this))
				.bind("set_rollback.jstree", $.proxy(function () { 
						this.reselect();
					}, this))
				.bind("close_node.jstree", $.proxy(function (event, data) { 
						var s = this._get_settings().ui,
							obj = this._get_node(data.rslt.obj),
							clk = (obj && obj.length) ? obj.children("ul").find(".jstree-clicked") : $(),
							_this = this;
						if(s.selected_parent_close === false || !clk.length) { return; }
						clk.each(function () { 
							_this.deselect_node(this);
							if(s.selected_parent_close === "select_parent") { _this.select_node(obj); }
						});
					}, this))
				.bind("delete_node.jstree", $.proxy(function (event, data) { 
						var s = this._get_settings().ui.select_prev_on_delete,
							obj = this._get_node(data.rslt.obj),
							clk = (obj && obj.length) ? obj.find(".jstree-clicked") : [],
							_this = this;
						clk.each(function () { _this.deselect_node(this); });
						if(s && clk.length) { this.select_node(data.rslt.prev); }
					}, this))
				.bind("move_node.jstree", $.proxy(function (event, data) { 
						if(data.rslt.cy) { 
							data.rslt.oc.find(".jstree-clicked").removeClass("jstree-clicked");
						}
					}, this));
		},
		defaults : {
			select_limit : -1, // 0, 1, 2 ... or -1 for unlimited
			select_multiple_modifier : "ctrl", // on, or ctrl, shift, alt
			selected_parent_close : "select_parent", // false, "deselect", "select_parent"
			select_prev_on_delete : true,
			disable_selecting_children : false,
			initially_select : []
		},
		_fn : { 
			_get_node : function (obj, allow_multiple) {
				if(typeof obj === "undefined" || obj === null) { return allow_multiple ? this.data.ui.selected : this.data.ui.last_selected; }
				var $obj = $(obj, this.get_container()); 
				if($obj.is(".jstree") || obj == -1) { return -1; } 
				$obj = $obj.closest("li", this.get_container()); 
				return $obj.length ? $obj : false; 
			},
			save_selected : function () {
				var _this = this;
				this.data.ui.to_select = [];
				this.data.ui.selected.each(function () { _this.data.ui.to_select.push("#" + this.id.toString().replace(/^#/,"").replace('\\/','/').replace('/','\\/')); });
				this.__callback(this.data.ui.to_select);
			},
			reselect : function () {
				var _this = this,
					s = this.data.ui.to_select;
				s = $.map($.makeArray(s), function (n) { return "#" + n.toString().replace(/^#/,"").replace('\\/','/').replace('/','\\/'); });
				this.deselect_all();
				$.each(s, function (i, val) { if(val && val !== "#") { _this.select_node(val); } });
				this.__callback();
			},
			refresh : function (obj) {
				this.save_selected();
				return this.__call_old();
			},
			hover_node : function (obj) {
				obj = this._get_node(obj);
				if(!obj.length) { return false; }
				//if(this.data.ui.hovered && obj.get(0) === this.data.ui.hovered.get(0)) { return; }
				if(!obj.hasClass("jstree-hovered")) { this.dehover_node(); }
				this.data.ui.hovered = obj.children("a").addClass("jstree-hovered").parent();
				this.__callback({ "obj" : obj });
			},
			dehover_node : function () {
				var obj = this.data.ui.hovered, p;
				if(!obj || !obj.length) { return false; }
				p = obj.children("a").removeClass("jstree-hovered").parent();
				if(this.data.ui.hovered[0] === p[0]) { this.data.ui.hovered = null; }
				this.__callback({ "obj" : obj });
			},
			select_node : function (obj, check, e) {
				obj = this._get_node(obj);
				if(obj == -1 || !obj || !obj.length) { return false; }
				var s = this._get_settings().ui,
					is_multiple = (s.select_multiple_modifier == "on" || (s.select_multiple_modifier !== false && e && e[s.select_multiple_modifier + "Key"])),
					is_selected = this.is_selected(obj),
					proceed = true;
				if(check) {
					if(s.disable_selecting_children && is_multiple && obj.parents("li", this.get_container()).children(".jstree-clicked").length) {
						return false;
					}
					proceed = false;
					switch(!0) {
						case (is_selected && !is_multiple): 
							this.deselect_all();
							is_selected = false;
							proceed = true;
							break;
						case (!is_selected && !is_multiple): 
							if(s.select_limit == -1 || s.select_limit > 0) {
								this.deselect_all();
								proceed = true;
							}
							break;
						case (is_selected && is_multiple): 
							this.deselect_node(obj);
							break;
						case (!is_selected && is_multiple): 
							if(s.select_limit == -1 || this.data.ui.selected.length + 1 <= s.select_limit) { 
								proceed = true;
							}
							break;
					}
				}
				if(proceed && !is_selected) {
					obj.children("a").addClass("jstree-clicked");
					this.data.ui.selected = this.data.ui.selected.add(obj);
					this.data.ui.last_selected = obj;
					this.__callback({ "obj" : obj });
				}
			},
			deselect_node : function (obj) {
				obj = this._get_node(obj);
				if(!obj.length) { return false; }
				if(this.is_selected(obj)) {
					obj.children("a").removeClass("jstree-clicked");
					this.data.ui.selected = this.data.ui.selected.not(obj);
					if(this.data.ui.last_selected.get(0) === obj.get(0)) { this.data.ui.last_selected = this.data.ui.selected.eq(0); }
					this.__callback({ "obj" : obj });
				}
			},
			toggle_select : function (obj) {
				obj = this._get_node(obj);
				if(!obj.length) { return false; }
				if(this.is_selected(obj)) { this.deselect_node(obj); }
				else { this.select_node(obj); }
			},
			is_selected : function (obj) { return this.data.ui.selected.index(this._get_node(obj)) >= 0; },
			get_selected : function (context) { 
				return context ? $(context).find(".jstree-clicked").parent() : this.data.ui.selected; 
			},
			deselect_all : function (context) {
				if(context) { $(context).find(".jstree-clicked").removeClass("jstree-clicked"); } 
				else { this.get_container().find(".jstree-clicked").removeClass("jstree-clicked"); }
				this.data.ui.selected = $([]);
				this.data.ui.last_selected = false;
				this.__callback();
			}
		}
	});
	// include the selection plugin by default
	$.jstree.defaults.plugins.push("ui");
})(jQuery);
//*/

/* 
 * jsTree CRRM plugin 1.0
 * Handles creating/renaming/removing/moving nodes by user interaction.
 */
(function ($) {
	$.jstree.plugin("crrm", { 
		__init : function () {
			this.get_container()
				.bind("move_node.jstree", $.proxy(function (e, data) {
					if(this._get_settings().crrm.move.open_onmove) {
						var t = this;
						data.rslt.np.parentsUntil(".jstree").andSelf().filter(".jstree-closed").each(function () {
							t.open_node(this, false, true);
						});
					}
				}, this));
		},
		defaults : {
			input_width_limit : 200,
			move : {
				always_copy			: false, // false, true or "multitree"
				open_onmove			: true,
				default_position	: "last",
				check_move			: function (m) { return true; }
			}
		},
		_fn : {
			_show_input : function (obj, callback) {
				obj = this._get_node(obj);
				var rtl = this._get_settings().core.rtl,
					w = this._get_settings().crrm.input_width_limit,
					w1 = obj.children("ins").width(),
					w2 = obj.find("> a:visible > ins").width() * obj.find("> a:visible > ins").length,
					t = this.get_text(obj),
					h1 = $("<div>", { css : { "position" : "absolute", "top" : "-200px", "left" : (rtl ? "0px" : "-1000px"), "visibility" : "hidden" } }).appendTo("body"),
					h2 = obj.css("position","relative").append(
					$("<input>", { 
						"value" : t,
						// "size" : t.length,
						"css" : {
							"padding" : "0",
							"border" : "1px solid silver",
							"position" : "absolute",
							"left"  : (rtl ? "auto" : (w1 + w2 + 4) + "px"),
							"right" : (rtl ? (w1 + w2 + 4) + "px" : "auto"),
							"top" : "0px",
							"height" : (this.data.core.li_height - 2) + "px",
							"lineHeight" : (this.data.core.li_height - 2) + "px",
							"width" : "150px" // will be set a bit further down
						},
						"blur" : $.proxy(function () {
							var i = obj.children("input"),
								v = i.val();
							if(v === "") { v = t; }
							i.remove(); // rollback purposes
							this.set_text(obj,t); // rollback purposes
							this.rename_node(obj, v);
							callback.call(this, obj, v, t);
							obj.css("position","");
						}, this),
						"keyup" : function (event) {
							var key = event.keyCode || event.which;
							if(key == 27) { this.value = t; this.blur(); return; }
							else if(key == 13) { this.blur(); return; }
							else {
								h2.width(Math.min(h1.text("pW" + this.value).width(),w));
							}
						}
					})
				).children("input"); 
				this.set_text(obj, "");
				h1.css({
						fontFamily		: h2.css('fontFamily')		|| '',
						fontSize		: h2.css('fontSize')		|| '',
						fontWeight		: h2.css('fontWeight')		|| '',
						fontStyle		: h2.css('fontStyle')		|| '',
						fontStretch		: h2.css('fontStretch')		|| '',
						fontVariant		: h2.css('fontVariant')		|| '',
						letterSpacing	: h2.css('letterSpacing')	|| '',
						wordSpacing		: h2.css('wordSpacing')		|| ''
				});
				h2.width(Math.min(h1.text("pW" + h2[0].value).width(),w))[0].select();
			},
			rename : function (obj) {
				obj = this._get_node(obj);
				this.__rollback();
				var f = this.__callback;
				this._show_input(obj, function (obj, new_name, old_name) { 
					f.call(this, { "obj" : obj, "new_name" : new_name, "old_name" : old_name });
				});
			},
			create : function (obj, position, js, callback, skip_rename) {
				var t, _this = this;
				obj = this._get_node(obj);
				if(!obj) { obj = -1; }
				this.__rollback();
				t = this.create_node(obj, position, js, function (t) {
					var p = this._get_parent(t),
						pos = $(t).index();
					if(callback) { callback.call(this, t); }
					if(p.length && p.hasClass("jstree-closed")) { this.open_node(p, false, true); }
					if(!skip_rename) { 
						this._show_input(t, function (obj, new_name, old_name) { 
							_this.__callback({ "obj" : obj, "name" : new_name, "parent" : p, "position" : pos });
						});
					}
					else { _this.__callback({ "obj" : t, "name" : this.get_text(t), "parent" : p, "position" : pos }); }
				});
				return t;
			},
			remove : function (obj) {
				obj = this._get_node(obj, true);
				this.__rollback();
				this.delete_node(obj);
				this.__callback({ "obj" : obj });
			},
			check_move : function () {
				if(!this.__call_old()) { return false; }
				var s = this._get_settings().crrm.move;
				if(!s.check_move.call(this, this._get_move())) { return false; }
				return true;
			},
			move_node : function (obj, ref, position, is_copy, is_prepared, skip_check) {
				var s = this._get_settings().crrm.move;
				if(!is_prepared) { 
					if(!position) { position = s.default_position; }
					if(position === "inside" && !s.default_position.match(/^(before|after)$/)) { position = s.default_position; }
					return this.__call_old(true, obj, ref, position, is_copy, false, skip_check);
				}
				// if the move is already prepared
				if(s.always_copy === true || (s.always_copy === "multitree" && obj.rt.get_index() !== obj.ot.get_index() )) {
					is_copy = true;
				}
				this.__call_old(true, obj, ref, position, is_copy, true, skip_check);
			},

			cut : function (obj) {
				obj = this._get_node(obj);
				this.data.crrm.cp_nodes = false;
				this.data.crrm.ct_nodes = false;
				if(!obj || !obj.length) { return false; }
				this.data.crrm.ct_nodes = obj;
			},
			copy : function (obj) {
				obj = this._get_node(obj);
				this.data.crrm.cp_nodes = false;
				this.data.crrm.ct_nodes = false;
				if(!obj || !obj.length) { return false; }
				this.data.crrm.cp_nodes = obj;
			},
			paste : function (obj) { 
				obj = this._get_node(obj);
				if(!obj || !obj.length) { return false; }
				if(!this.data.crrm.ct_nodes && !this.data.crrm.cp_nodes) { return false; }
				if(this.data.crrm.ct_nodes) { this.move_node(this.data.crrm.ct_nodes, obj); }
				if(this.data.crrm.cp_nodes) { this.move_node(this.data.crrm.cp_nodes, obj, false, true); }
				this.data.crrm.cp_nodes = false;
				this.data.crrm.ct_nodes = false;
			}
		}
	});
	// include the crr plugin by default
	$.jstree.defaults.plugins.push("crrm");
})(jQuery);

/* 
 * jsTree themes plugin 1.0
 * Handles loading and setting themes, as well as detecting path to themes, etc.
 */
(function ($) {
	var themes_loaded = [];
	// this variable stores the path to the themes folder - if left as false - it will be autodetected
	$.jstree._themes = false;
	$.jstree.plugin("themes", {
		__init : function () { 
			this.get_container()
				.bind("init.jstree", $.proxy(function () {
						var s = this._get_settings().themes;
						this.data.themes.dots = s.dots; 
						this.data.themes.icons = s.icons; 
						//loggedAlert(s.dots);
						this.set_theme(s.theme, s.url);
					}, this))
				.bind("loaded.jstree", $.proxy(function () {
						// bound here too, as simple HTML tree's won't honor dots & icons otherwise
						if(!this.data.themes.dots) { this.hide_dots(); }
						else { this.show_dots(); }
						if(!this.data.themes.icons) { this.hide_icons(); }
						else { this.show_icons(); }
					}, this));
		},
		defaults : { 
			theme : "default", 
			url : false,
			dots : true,
			icons : true
		},
		_fn : {
			set_theme : function (theme_name, theme_url) {
				if(!theme_name) { return false; }
				if(!theme_url) { theme_url = $.jstree._themes + theme_name + '/style.css'; }
				if($.inArray(theme_url, themes_loaded) == -1) {
					$.vakata.css.add_sheet({ "url" : theme_url, "rel" : "jstree" });
					themes_loaded.push(theme_url);
				}
				if(this.data.themes.theme != theme_name) {
					this.get_container().removeClass('jstree-' + this.data.themes.theme);
					this.data.themes.theme = theme_name;
				}
				this.get_container().addClass('jstree-' + theme_name);
				if(!this.data.themes.dots) { this.hide_dots(); }
				else { this.show_dots(); }
				if(!this.data.themes.icons) { this.hide_icons(); }
				else { this.show_icons(); }
				this.__callback();
			},
			get_theme	: function () { return this.data.themes.theme; },

			show_dots	: function () { this.data.themes.dots = true; this.get_container().children("ul").removeClass("jstree-no-dots"); },
			hide_dots	: function () { this.data.themes.dots = false; this.get_container().children("ul").addClass("jstree-no-dots"); },
			toggle_dots	: function () { if(this.data.themes.dots) { this.hide_dots(); } else { this.show_dots(); } },

			show_icons	: function () { this.data.themes.icons = true; this.get_container().children("ul").removeClass("jstree-no-icons"); },
			hide_icons	: function () { this.data.themes.icons = false; this.get_container().children("ul").addClass("jstree-no-icons"); },
			toggle_icons: function () { if(this.data.themes.icons) { this.hide_icons(); } else { this.show_icons(); } }
		}
	});
	// autodetect themes path
	$(function () {
		if($.jstree._themes === false) {
			$("script").each(function () { 
				if(this.src.toString().match(/jquery\.jstree[^\/]*?\.js(\?.*)?$/)) { 
					$.jstree._themes = this.src.toString().replace(/jquery\.jstree[^\/]*?\.js(\?.*)?$/, "") + 'themes/'; 
					return false; 
				}
			});
		}
		if($.jstree._themes === false) { $.jstree._themes = "themes/"; }
	});
	// include the themes plugin by default
	$.jstree.defaults.plugins.push("themes");
})(jQuery);
//*/

/*
 * jsTree hotkeys plugin 1.0
 * Enables keyboard navigation for all tree instances
 * Depends on the jstree ui & jquery hotkeys plugins
 */
(function ($) {
	var bound = [];
	function exec(i, event) {
		var f = $.jstree._focused(), tmp;
		if(f && f.data && f.data.hotkeys && f.data.hotkeys.enabled) { 
			tmp = f._get_settings().hotkeys[i];
			if(tmp) { return tmp.call(f, event); }
		}
	}
	$.jstree.plugin("hotkeys", {
		__init : function () {
			if(typeof $.hotkeys === "undefined") { throw "jsTree hotkeys: jQuery hotkeys plugin not included."; }
			if(!this.data.ui) { throw "jsTree hotkeys: jsTree UI plugin not included."; }
			$.each(this._get_settings().hotkeys, function (i, val) {
				if($.inArray(i, bound) == -1) {
					$(document).bind("keydown", i, function (event) { return exec(i, event); });
					bound.push(i);
				}
			});
			this.enable_hotkeys();
		},
		defaults : {
			"up" : function () { 
				var o = this.data.ui.hovered || this.data.ui.last_selected || -1;
				this.hover_node(this._get_prev(o));
				return false; 
			},
			"down" : function () { 
				var o = this.data.ui.hovered || this.data.ui.last_selected || -1;
				this.hover_node(this._get_next(o));
				return false;
			},
			"left" : function () { 
				var o = this.data.ui.hovered || this.data.ui.last_selected;
				if(o) {
					if(o.hasClass("jstree-open")) { this.close_node(o); }
					else { this.hover_node(this._get_prev(o)); }
				}
				return false;
			},
			"right" : function () { 
				var o = this.data.ui.hovered || this.data.ui.last_selected;
				if(o && o.length) {
					if(o.hasClass("jstree-closed")) { this.open_node(o); }
					else { this.hover_node(this._get_next(o)); }
				}
				return false;
			},
			"space" : function () { 
				if(this.data.ui.hovered) { this.data.ui.hovered.children("a:eq(0)").click(); } 
				return false; 
			},
			"ctrl+space" : function (event) { 
				event.type = "click";
				if(this.data.ui.hovered) { this.data.ui.hovered.children("a:eq(0)").trigger(event); } 
				return false; 
			},
			"f2" : function () { this.rename(this.data.ui.hovered || this.data.ui.last_selected); },
			"del" : function () { this.remove(this.data.ui.hovered || this._get_node(null)); }
		},
		_fn : {
			enable_hotkeys : function () {
				this.data.hotkeys.enabled = true;
			},
			disable_hotkeys : function () {
				this.data.hotkeys.enabled = false;
			}
		}
	});
})(jQuery);
//*/

/* 
 * jsTree JSON 1.0
 * The JSON data store. Datastores are build by overriding the `load_node` and `_is_loaded` functions.
 */
(function ($) {
	$.jstree.plugin("json_data", {
		defaults : { 
			data : false,
			ajax : false,
			correct_state : true,
			progressive_render : false
		},
		_fn : {
			load_node : function (obj, s_call, e_call) { var _this = this; this.load_node_json(obj, function () { _this.__callback({ "obj" : obj }); s_call.call(this); }, e_call); },
			_is_loaded : function (obj) { 
				var s = this._get_settings().json_data, d;
				obj = this._get_node(obj); 
				if(obj && obj !== -1 && s.progressive_render && !obj.is(".jstree-open, .jstree-leaf") && obj.children("ul").children("li").length === 0 && obj.data("jstree-children")) {
					d = this._parse_json(obj.data("jstree-children"));
					if(d) {
						obj.append(d);
						$.removeData(obj, "jstree-children");
					}
					this.clean_node(obj);
					return true;
				}
				return obj == -1 || !obj || !s.ajax || obj.is(".jstree-open, .jstree-leaf") || obj.children("ul").children("li").size() > 0;
			},
			load_node_json : function (obj, s_call, e_call) {
				var s = this.get_settings().json_data, d,
					error_func = function () {},
					success_func = function () {};
				obj = this._get_node(obj);
				if(obj && obj !== -1) {
					if(obj.data("jstree-is-loading")) { return; }
					else { obj.data("jstree-is-loading",true); }
				}
				switch(!0) {
					case (!s.data && !s.ajax): throw "Neither data nor ajax settings supplied.";
					case (!!s.data && !s.ajax) || (!!s.data && !!s.ajax && (!obj || obj === -1)):
						if(!obj || obj == -1) {
							d = this._parse_json(s.data);
							if(d) {
								this.get_container().children("ul").empty().append(d.children());
								this.clean_node();
							}
							else { 
								if(s.correct_state) { this.get_container().children("ul").empty(); }
							}
						}
						if(s_call) { s_call.call(this); }
						break;
					case (!s.data && !!s.ajax) || (!!s.data && !!s.ajax && obj && obj !== -1):
						error_func = function (x, t, e) {
							var ef = this.get_settings().json_data.ajax.error; 
							if(ef) { ef.call(this, x, t, e); }
							if(obj != -1 && obj.length) {
								obj.children(".jstree-loading").removeClass("jstree-loading");
								obj.data("jstree-is-loading",false);
								if(t === "success" && s.correct_state) { obj.removeClass("jstree-open jstree-closed").addClass("jstree-leaf"); }
							}
							else {
								if(t === "success" && s.correct_state) { this.get_container().children("ul").empty(); }
							}
							if(e_call) { e_call.call(this); }
						};
						success_func = function (d, t, x) {
							var sf = this.get_settings().json_data.ajax.success; 
							if(sf) { d = sf.call(this,d,t,x) || d; }
							if(d === "" || (!$.isArray(d) && !$.isPlainObject(d))) {
								return error_func.call(this, x, t, "");
							}
							d = this._parse_json(d);
							if(d) {
								if(obj === -1 || !obj) { this.get_container().children("ul").empty().append(d.children()); }
								else { obj.append(d).children(".jstree-loading").removeClass("jstree-loading"); obj.data("jstree-is-loading",false); }
								this.clean_node(obj);
								if(s_call) { s_call.call(this); }
							}
							else {
								if(obj === -1 || !obj) {
									if(s.correct_state) { 
										this.get_container().children("ul").empty(); 
										if(s_call) { s_call.call(this); }
									}
								}
								else {
									obj.children(".jstree-loading").removeClass("jstree-loading");
									obj.data("jstree-is-loading",false);
									if(s.correct_state) { 
										obj.removeClass("jstree-open jstree-closed").addClass("jstree-leaf"); 
										if(s_call) { s_call.call(this); } 
									}
								}
							}
						};
						s.ajax.context = this;
						s.ajax.error = error_func;
						s.ajax.success = success_func;
						if(!s.ajax.dataType) { s.ajax.dataType = "json"; }
						if($.isFunction(s.ajax.url)) { s.ajax.url = s.ajax.url.call(this, obj); }
						if($.isFunction(s.ajax.data)) { s.ajax.data = s.ajax.data.call(this, obj); }
						$.ajax(s.ajax);
						break;
				}
			},
			_parse_json : function (js, is_callback) {
				var d = false, 
					p = this._get_settings(),
					s = p.json_data,
					t = p.core.html_titles,
					tmp, i, j, ul1, ul2;

				if(!js) { return d; }
				if($.isFunction(js)) { 
					js = js.call(this);
				}
				if($.isArray(js)) {
					d = $();
					if(!js.length) { return false; }
					for(i = 0, j = js.length; i < j; i++) {
						tmp = this._parse_json(js[i], true);
						if(tmp.length) { d = d.add(tmp); }
					}
				}
				else {
					if(typeof js == "string") { js = { data : js }; }
					if(!js.data && js.data !== "") { return d; }
					d = $("<li>");
					if(js.attr) { d.attr(js.attr); }
					if(js.metadata) { d.data("jstree", js.metadata); }
					if(js.state) { d.addClass("jstree-" + js.state); }
					if(!$.isArray(js.data)) { tmp = js.data; js.data = []; js.data.push(tmp); }
					$.each(js.data, function (i, m) {
						tmp = $("<a>");
						if($.isFunction(m)) { m = m.call(this, js); }
						if(typeof m == "string") { tmp.attr('href','#')[ t ? "html" : "text" ](m); }
						else {
							if(!m.attr) { m.attr = {}; }
							if(!m.attr.href) { m.attr.href = '#'; }
							tmp.attr(m.attr)[ t ? "html" : "text" ](m.title);
							if(m.language) { tmp.addClass(m.language); }
						}
						tmp.prepend("<ins class='jstree-icon'>&#160;</ins>");
						if(!m.icon && js.icon) { m.icon = js.icon; }
						if(m.icon) { 
							if(m.icon.indexOf("/") === -1) { tmp.children("ins").addClass(m.icon); }
							else { tmp.children("ins").css("background","url('" + m.icon + "') center center no-repeat"); }
						}
						d.append(tmp);
					});
					d.prepend("<ins class='jstree-icon'>&#160;</ins>");
					if(js.children) { 
						if(s.progressive_render && js.state !== "open") {
							d.addClass("jstree-closed").data("jstree-children", js.children);
						}
						else {
							if($.isFunction(js.children)) {
								js.children = js.children.call(this, js);
							}
							if($.isArray(js.children) && js.children.length) {
								tmp = this._parse_json(js.children, true);
								if(tmp.length) {
									ul2 = $("<ul>");
									ul2.append(tmp);
									d.append(ul2);
								}
							}
						}
					}
				}
				if(!is_callback) {
					ul1 = $("<ul>");
					ul1.append(d);
					d = ul1;
				}
				return d;
			},
			get_json : function (obj, li_attr, a_attr, is_callback) {
				var result = [], 
					s = this._get_settings(), 
					_this = this,
					tmp1, tmp2, li, a, t, lang;
				obj = this._get_node(obj);
				if(!obj || obj === -1) { obj = this.get_container().find("> ul > li"); }
				li_attr = $.isArray(li_attr) ? li_attr : [ "id", "class" ];
				if(!is_callback && this.data.types) { li_attr.push(s.types.type_attr); }
				a_attr = $.isArray(a_attr) ? a_attr : [ ];

				obj.each(function () {
					li = $(this);
					tmp1 = { data : [] };
					if(li_attr.length) { tmp1.attr = { }; }
					$.each(li_attr, function (i, v) { 
						tmp2 = li.attr(v); 
						if(tmp2 && tmp2.length && tmp2.replace(/jstree[^ ]*|$/ig,'').length) {
							tmp1.attr[v] = tmp2.replace(/jstree[^ ]*|$/ig,''); 
						}
					});
					if(li.hasClass("jstree-open")) { tmp1.state = "open"; }
					if(li.hasClass("jstree-closed")) { tmp1.state = "closed"; }
					a = li.children("a");
					a.each(function () {
						t = $(this);
						if(
							a_attr.length || 
							$.inArray("languages", s.plugins) !== -1 || 
							t.children("ins").get(0).style.backgroundImage.length || 
							(t.children("ins").get(0).className && t.children("ins").get(0).className.replace(/jstree[^ ]*|$/ig,'').length)
						) { 
							lang = false;
							if($.inArray("languages", s.plugins) !== -1 && $.isArray(s.languages) && s.languages.length) {
								$.each(s.languages, function (l, lv) {
									if(t.hasClass(lv)) {
										lang = lv;
										return false;
									}
								});
							}
							tmp2 = { attr : { }, title : _this.get_text(t, lang) }; 
							$.each(a_attr, function (k, z) {
								tmp1.attr[z] = (t.attr(z) || "").replace(/jstree[^ ]*|$/ig,'');
							});
							$.each(s.languages, function (k, z) {
								if(t.hasClass(z)) { tmp2.language = z; return true; }
							});
							if(t.children("ins").get(0).className.replace(/jstree[^ ]*|$/ig,'').replace(/^\s+$/ig,"").length) {
								tmp2.icon = t.children("ins").get(0).className.replace(/jstree[^ ]*|$/ig,'').replace(/^\s+$/ig,"");
							}
							if(t.children("ins").get(0).style.backgroundImage.length) {
								tmp2.icon = t.children("ins").get(0).style.backgroundImage.replace("url(","").replace(")","");
							}
						}
						else {
							tmp2 = _this.get_text(t);
						}
						if(a.length > 1) { tmp1.data.push(tmp2); }
						else { tmp1.data = tmp2; }
					});
					li = li.find("> ul > li");
					if(li.length) { tmp1.children = _this.get_json(li, li_attr, a_attr, true); }
					result.push(tmp1);
				});
				return result;
			}
		}
	});
})(jQuery);
//*/

/* 
 * jsTree languages plugin 1.0
 * Adds support for multiple language versions in one tree
 * This basically allows for many titles coexisting in one node, but only one of them being visible at any given time
 * This is useful for maintaining the same structure in many languages (hence the name of the plugin)
 */
(function ($) {
	$.jstree.plugin("languages", {
		__init : function () { this._load_css();  },
		defaults : [],
		_fn : {
			set_lang : function (i) { 
				var langs = this._get_settings().languages,
					st = false,
					selector = ".jstree-" + this.get_index() + ' a';
				if(!$.isArray(langs) || langs.length === 0) { return false; }
				if($.inArray(i,langs) == -1) {
					if(!!langs[i]) { i = langs[i]; }
					else { return false; }
				}
				if(i == this.data.languages.current_language) { return true; }
				st = $.vakata.css.get_css(selector + "." + this.data.languages.current_language, false, this.data.languages.language_css);
				if(st !== false) { st.style.display = "none"; }
				st = $.vakata.css.get_css(selector + "." + i, false, this.data.languages.language_css);
				if(st !== false) { st.style.display = ""; }
				this.data.languages.current_language = i;
				this.__callback(i);
				return true;
			},
			get_lang : function () {
				return this.data.languages.current_language;
			},
			get_text : function (obj, lang) {
				obj = this._get_node(obj) || this.data.ui.last_selected;
				if(!obj.size()) { return false; }
				var langs = this._get_settings().languages,
					s = this._get_settings().core.html_titles;
				if($.isArray(langs) && langs.length) {
					lang = (lang && $.inArray(lang,langs) != -1) ? lang : this.data.languages.current_language;
					obj = obj.children("a." + lang);
				}
				else { obj = obj.children("a:eq(0)"); }
				if(s) {
					obj = obj.clone();
					obj.children("INS").remove();
					return obj.html();
				}
				else {
					obj = obj.contents().filter(function() { return this.nodeType == 3; })[0];
					return obj.nodeValue;
				}
			},
			set_text : function (obj, val, lang) {
				obj = this._get_node(obj) || this.data.ui.last_selected;
				if(!obj.size()) { return false; }
				var langs = this._get_settings().languages,
					s = this._get_settings().core.html_titles,
					tmp;
				if($.isArray(langs) && langs.length) {
					lang = (lang && $.inArray(lang,langs) != -1) ? lang : this.data.languages.current_language;
					obj = obj.children("a." + lang);
				}
				else { obj = obj.children("a:eq(0)"); }
				if(s) {
					tmp = obj.children("INS").clone();
					obj.html(val).prepend(tmp);
					this.__callback({ "obj" : obj, "name" : val, "lang" : lang });
					return true;
				}
				else {
					obj = obj.contents().filter(function() { return this.nodeType == 3; })[0];
					this.__callback({ "obj" : obj, "name" : val, "lang" : lang });
					return (obj.nodeValue = val);
				}
			},
			_load_css : function () {
				var langs = this._get_settings().languages,
					str = "/* languages css */",
					selector = ".jstree-" + this.get_index() + ' a',
					ln;
				if($.isArray(langs) && langs.length) {
					this.data.languages.current_language = langs[0];
					for(ln = 0; ln < langs.length; ln++) {
						str += selector + "." + langs[ln] + " {";
						if(langs[ln] != this.data.languages.current_language) { str += " display:none; "; }
						str += " } ";
					}
					this.data.languages.language_css = $.vakata.css.add_sheet({ 'str' : str });
				}
			},
			create_node : function (obj, position, js, callback) {
				var t = this.__call_old(true, obj, position, js, function (t) {
					var langs = this._get_settings().languages,
						a = t.children("a"),
						ln;
					if($.isArray(langs) && langs.length) {
						for(ln = 0; ln < langs.length; ln++) {
							if(!a.is("." + langs[ln])) {
								t.append(a.eq(0).clone().removeClass(langs.join(" ")).addClass(langs[ln]));
							}
						}
						a.not("." + langs.join(", .")).remove();
					}
					if(callback) { callback.call(this, t); }
				});
				return t;
			}
		}
	});
})(jQuery);
//*/

/*
 * jsTree cookies plugin 1.0
 * Stores the currently opened/selected nodes in a cookie and then restores them
 * Depends on the jquery.cookie plugin
 */
(function ($) {
	$.jstree.plugin("cookies", {
		__init : function () {
			if(typeof $.cookie === "undefined") { throw "jsTree cookie: jQuery cookie plugin not included."; }

			var s = this._get_settings().cookies,
				tmp;
			if(!!s.save_opened) {
				tmp = $.cookie(s.save_opened);
				if(tmp && tmp.length) { this.data.core.to_open = tmp.split(","); }
			}
			if(!!s.save_selected) {
				tmp = $.cookie(s.save_selected);
				if(tmp && tmp.length && this.data.ui) { this.data.ui.to_select = tmp.split(","); }
			}
			this.get_container()
				.one( ( this.data.ui ? "reselect" : "reopen" ) + ".jstree", $.proxy(function () {
					this.get_container()
						.bind("open_node.jstree close_node.jstree select_node.jstree deselect_node.jstree", $.proxy(function (e) { 
								if(this._get_settings().cookies.auto_save) { this.save_cookie((e.handleObj.namespace + e.handleObj.type).replace("jstree","")); }
							}, this));
				}, this));
		},
		defaults : {
			save_opened		: "jstree_open",
			save_selected	: "jstree_select",
			auto_save		: true,
			cookie_options	: {}
		},
		_fn : {
			save_cookie : function (c) {
				if(this.data.core.refreshing) { return; }
				var s = this._get_settings().cookies;
				if(!c) { // if called manually and not by event
					if(s.save_opened) {
						this.save_opened();
						$.cookie(s.save_opened, this.data.core.to_open.join(","), s.cookie_options);
					}
					if(s.save_selected && this.data.ui) {
						this.save_selected();
						$.cookie(s.save_selected, this.data.ui.to_select.join(","), s.cookie_options);
					}
					return;
				}
				switch(c) {
					case "open_node":
					case "close_node":
						if(!!s.save_opened) { 
							this.save_opened(); 
							$.cookie(s.save_opened, this.data.core.to_open.join(","), s.cookie_options); 
						}
						break;
					case "select_node":
					case "deselect_node":
						if(!!s.save_selected && this.data.ui) { 
							this.save_selected(); 
							$.cookie(s.save_selected, this.data.ui.to_select.join(","), s.cookie_options); 
						}
						break;
				}
			}
		}
	});
	// include cookies by default
	$.jstree.defaults.plugins.push("cookies");
})(jQuery);
//*/

/*
 * jsTree sort plugin 1.0
 * Sorts items alphabetically (or using any other function)
 */
(function ($) {
	$.jstree.plugin("sort", {
		__init : function () {
			this.get_container()
				.bind("load_node.jstree", $.proxy(function (e, data) {
						var obj = this._get_node(data.rslt.obj);
						obj = obj === -1 ? this.get_container().children("ul") : obj.children("ul");
						this.sort(obj);
					}, this))
				.bind("rename_node.jstree", $.proxy(function (e, data) {
						this.sort(data.rslt.obj.parent());
					}, this))
				.bind("move_node.jstree", $.proxy(function (e, data) {
						var m = data.rslt.np == -1 ? this.get_container() : data.rslt.np;
						this.sort(m.children("ul"));
					}, this));
		},
		defaults : function (a, b) { return this.get_text(a) > this.get_text(b) ? 1 : -1; },
		_fn : {
			sort : function (obj) {
				var s = this._get_settings().sort,
					t = this;
				obj.append($.makeArray(obj.children("li")).sort($.proxy(s, t)));
				obj.find("> li > ul").each(function() { t.sort($(this)); });
				this.clean_node(obj);
			}
		}
	});
})(jQuery);
//*/

/*
 * jsTree DND plugin 1.0
 * Drag and drop plugin for moving/copying nodes
 */
(function ($) {
	var o = false,
		r = false,
		m = false,
		sli = false,
		sti = false,
		dir1 = false,
		dir2 = false;
	$.vakata.dnd = {
		is_down : false,
		is_drag : false,
		helper : false,
		scroll_spd : 10,
		init_x : 0,
		init_y : 0,
		threshold : 5,
		user_data : {},

		drag_start : function (e, data, html) { 
			if($.vakata.dnd.is_drag) { $.vakata.drag_stop({}); }
			try {
				e.currentTarget.unselectable = "on";
				e.currentTarget.onselectstart = function() { return false; };
				if(e.currentTarget.style) { e.currentTarget.style.MozUserSelect = "none"; }
			} catch(err) { }
			$.vakata.dnd.init_x = e.pageX;
			$.vakata.dnd.init_y = e.pageY;
			$.vakata.dnd.user_data = data;
			$.vakata.dnd.is_down = true;
			$.vakata.dnd.helper = $("<div id='vakata-dragged'>").html(html).css("opacity", "0.75");
			$(document).bind("mousemove", $.vakata.dnd.drag);
			$(document).bind("mouseup", $.vakata.dnd.drag_stop);
			return false;
		},
		drag : function (e) { 
			if(!$.vakata.dnd.is_down) { return; }
			if(!$.vakata.dnd.is_drag) {
				if(Math.abs(e.pageX - $.vakata.dnd.init_x) > 5 || Math.abs(e.pageY - $.vakata.dnd.init_y) > 5) { 
					$.vakata.dnd.helper.appendTo("body");
					$.vakata.dnd.is_drag = true;
					$(document).triggerHandler("drag_start.vakata", { "event" : e, "data" : $.vakata.dnd.user_data });
				}
				else { return; }
			}

			// maybe use a scrolling parent element instead of document?
			if(e.type === "mousemove") { // thought of adding scroll in order to move the helper, but mouse poisition is n/a
				var d = $(document), t = d.scrollTop(), l = d.scrollLeft();
				if(e.pageY - t < 20) { 
					if(sti && dir1 === "down") { clearInterval(sti); sti = false; }
					if(!sti) { dir1 = "up"; sti = setInterval(function () { $(document).scrollTop($(document).scrollTop() - $.vakata.dnd.scroll_spd); }, 150); }
				}
				else { 
					if(sti && dir1 === "up") { clearInterval(sti); sti = false; }
				}
				if($(window).height() - (e.pageY - t) < 20) {
					if(sti && dir1 === "up") { clearInterval(sti); sti = false; }
					if(!sti) { dir1 = "down"; sti = setInterval(function () { $(document).scrollTop($(document).scrollTop() + $.vakata.dnd.scroll_spd); }, 150); }
				}
				else { 
					if(sti && dir1 === "down") { clearInterval(sti); sti = false; }
				}

				if(e.pageX - l < 20) {
					if(sli && dir2 === "right") { clearInterval(sli); sli = false; }
					if(!sli) { dir2 = "left"; sli = setInterval(function () { $(document).scrollLeft($(document).scrollLeft() - $.vakata.dnd.scroll_spd); }, 150); }
				}
				else { 
					if(sli && dir2 === "left") { clearInterval(sli); sli = false; }
				}
				if($(window).width() - (e.pageX - l) < 20) {
					if(sli && dir2 === "left") { clearInterval(sli); sli = false; }
					if(!sli) { dir2 = "right"; sli = setInterval(function () { $(document).scrollLeft($(document).scrollLeft() + $.vakata.dnd.scroll_spd); }, 150); }
				}
				else { 
					if(sli && dir2 === "right") { clearInterval(sli); sli = false; }
				}
			}

			$.vakata.dnd.helper.css({ left : (e.pageX + 5) + "px", top : (e.pageY + 10) + "px" });
			$(document).triggerHandler("drag.vakata", { "event" : e, "data" : $.vakata.dnd.user_data });
		},
		drag_stop : function (e) {
			$(document).unbind("mousemove", $.vakata.dnd.drag);
			$(document).unbind("mouseup", $.vakata.dnd.drag_stop);
			$(document).triggerHandler("drag_stop.vakata", { "event" : e, "data" : $.vakata.dnd.user_data });
			$.vakata.dnd.helper.remove();
			$.vakata.dnd.init_x = 0;
			$.vakata.dnd.init_y = 0;
			$.vakata.dnd.user_data = {};
			$.vakata.dnd.is_down = false;
			$.vakata.dnd.is_drag = false;
		}
	};
	$(function() {
		var css_string = '#vakata-dragged { display:block; margin:0 0 0 0; padding:4px 4px 4px 24px; position:absolute; top:-2000px; line-height:16px; z-index:10000; } ';
		$.vakata.css.add_sheet({ str : css_string });
	});

	$.jstree.plugin("dnd", {
		__init : function () {
			this.data.dnd = {
				active : false,
				after : false,
				inside : false,
				before : false,
				off : false,
				prepared : false,
				w : 0,
				to1 : false,
				to2 : false,
				cof : false,
				cw : false,
				ch : false,
				i1 : false,
				i2 : false
			};
			this.get_container()
				.bind("mouseenter.jstree", $.proxy(function () {
						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree && this.data.themes) {
							m.attr("class", "jstree-" + this.data.themes.theme); 
							$.vakata.dnd.helper.attr("class", "jstree-dnd-helper jstree-" + this.data.themes.theme);
						}
					}, this))
				.bind("mouseleave.jstree", $.proxy(function () {
						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree) {
							if(this.data.dnd.i1) { clearInterval(this.data.dnd.i1); }
							if(this.data.dnd.i2) { clearInterval(this.data.dnd.i2); }
						}
					}, this))
				.bind("mousemove.jstree", $.proxy(function (e) {
						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree) {
							var cnt = this.get_container()[0];

							// Horizontal scroll
							if(e.pageX + 24 > this.data.dnd.cof.left + this.data.dnd.cw) {
								if(this.data.dnd.i1) { clearInterval(this.data.dnd.i1); }
								this.data.dnd.i1 = setInterval($.proxy(function () { this.scrollLeft += $.vakata.dnd.scroll_spd; }, cnt), 100);
							}
							else if(e.pageX - 24 < this.data.dnd.cof.left) {
								if(this.data.dnd.i1) { clearInterval(this.data.dnd.i1); }
								this.data.dnd.i1 = setInterval($.proxy(function () { this.scrollLeft -= $.vakata.dnd.scroll_spd; }, cnt), 100);
							}
							else {
								if(this.data.dnd.i1) { clearInterval(this.data.dnd.i1); }
							}

							// Vertical scroll
							if(e.pageY + 24 > this.data.dnd.cof.top + this.data.dnd.ch) {
								if(this.data.dnd.i2) { clearInterval(this.data.dnd.i2); }
								this.data.dnd.i2 = setInterval($.proxy(function () { this.scrollTop += $.vakata.dnd.scroll_spd; }, cnt), 100);
							}
							else if(e.pageY - 24 < this.data.dnd.cof.top) {
								if(this.data.dnd.i2) { clearInterval(this.data.dnd.i2); }
								this.data.dnd.i2 = setInterval($.proxy(function () { this.scrollTop -= $.vakata.dnd.scroll_spd; }, cnt), 100);
							}
							else {
								if(this.data.dnd.i2) { clearInterval(this.data.dnd.i2); }
							}

						}
					}, this))
				.delegate("a", "mousedown.jstree", $.proxy(function (e) { 
						if(e.which === 1) {
							this.start_drag(e.currentTarget, e);
							return false;
						}
					}, this))
				.delegate("a", "mouseenter.jstree", $.proxy(function (e) { 
						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree) {
							this.dnd_enter(e.currentTarget);
						}
					}, this))
				.delegate("a", "mousemove.jstree", $.proxy(function (e) { 
						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree) {
							if(typeof this.data.dnd.off.top === "undefined") { this.data.dnd.off = $(e.target).offset(); }
							this.data.dnd.w = (e.pageY - (this.data.dnd.off.top || 0)) % this.data.core.li_height;
							if(this.data.dnd.w < 0) { this.data.dnd.w += this.data.core.li_height; }
							this.dnd_show();
						}
					}, this))
				.delegate("a", "mouseleave.jstree", $.proxy(function (e) { 
						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree) {
							this.data.dnd.after		= false;
							this.data.dnd.before	= false;
							this.data.dnd.inside	= false;
							$.vakata.dnd.helper.children("ins").attr("class","jstree-invalid");
							m.hide();
							if(r && r[0] === e.target.parentNode) {
								if(this.data.dnd.to1) {
									clearTimeout(this.data.dnd.to1);
									this.data.dnd.to1 = false;
								}
								if(this.data.dnd.to2) {
									clearTimeout(this.data.dnd.to2);
									this.data.dnd.to2 = false;
								}
							}
						}
					}, this))
				.delegate("a", "mouseup.jstree", $.proxy(function (e) { 
						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree) {
							this.dnd_finish(e);
						}
					}, this));

			$(document)
				.bind("drag_stop.vakata", $.proxy(function () {
						this.data.dnd.after		= false;
						this.data.dnd.before	= false;
						this.data.dnd.inside	= false;
						this.data.dnd.off		= false;
						this.data.dnd.prepared	= false;
						this.data.dnd.w			= false;
						this.data.dnd.to1		= false;
						this.data.dnd.to2		= false;
						this.data.dnd.active	= false;
						this.data.dnd.foreign	= false;
						if(m) { m.css({ "top" : "-2000px" }); }
					}, this))
				.bind("drag_start.vakata", $.proxy(function (e, data) {
						if(data.data.jstree) { 
							var et = $(data.event.target);
							if(et.closest(".jstree").hasClass("jstree-" + this.get_index())) {
								this.dnd_enter(et);
							}
						}
					}, this));

			var s = this._get_settings().dnd;
			if(s.drag_target) {
				$(document)
					.delegate(s.drag_target, "mousedown.jstree", $.proxy(function (e) {
						o = e.target;
						$.vakata.dnd.drag_start(e, { jstree : true, obj : e.target }, "<ins class='jstree-icon'></ins>" + $(e.target).text() );
						if(this.data.themes) { 
							m.attr("class", "jstree-" + this.data.themes.theme); 
							$.vakata.dnd.helper.attr("class", "jstree-dnd-helper jstree-" + this.data.themes.theme); 
						}
						$.vakata.dnd.helper.children("ins").attr("class","jstree-invalid");
						var cnt = this.get_container();
						this.data.dnd.cof = cnt.offset();
						this.data.dnd.cw = parseInt(cnt.width(),10);
						this.data.dnd.ch = parseInt(cnt.height(),10);
						this.data.dnd.foreign = true;
						return false;
					}, this));
			}
			if(s.drop_target) {
				$(document)
					.delegate(s.drop_target, "mouseenter.jstree", $.proxy(function (e) {
							if(this.data.dnd.active && this._get_settings().dnd.drop_check.call(this, { "o" : o, "r" : $(e.target) })) {
								$.vakata.dnd.helper.children("ins").attr("class","jstree-ok");
							}
						}, this))
					.delegate(s.drop_target, "mouseleave.jstree", $.proxy(function (e) {
							if(this.data.dnd.active) {
								$.vakata.dnd.helper.children("ins").attr("class","jstree-invalid");
							}
						}, this))
					.delegate(s.drop_target, "mouseup.jstree", $.proxy(function (e) {
							if(this.data.dnd.active && $.vakata.dnd.helper.children("ins").hasClass("jstree-ok")) {
								this._get_settings().dnd.drop_finish.call(this, { "o" : o, "r" : $(e.target) });
							}
						}, this));
			}
		},
		defaults : {
			copy_modifier	: "ctrl",
			check_timeout	: 200,
			open_timeout	: 500,
			drop_target		: ".jstree-drop",
			drop_check		: function (data) { return true; },
			drop_finish		: $.noop,
			drag_target		: ".jstree-draggable",
			drag_finish		: $.noop,
			drag_check		: function (data) { return { after : false, before : false, inside : true }; }
		},
		_fn : {
			dnd_prepare : function () {
				if(!r || !r.length) { return; }
				this.data.dnd.off = r.offset();
				if(this._get_settings().core.rtl) {
					this.data.dnd.off.right = this.data.dnd.off.left + r.width();
				}
				if(this.data.dnd.foreign) {
					var a = this._get_settings().dnd.drag_check.call(this, { "o" : o, "r" : r });
					this.data.dnd.after = a.after;
					this.data.dnd.before = a.before;
					this.data.dnd.inside = a.inside;
					this.data.dnd.prepared = true;
					return this.dnd_show();
				}
				this.prepare_move(o, r, "before");
				this.data.dnd.before = this.check_move();
				this.prepare_move(o, r, "after");
				this.data.dnd.after = this.check_move();
				if(this._is_loaded(r)) {
					this.prepare_move(o, r, "inside");
					this.data.dnd.inside = this.check_move();
				}
				else {
					this.data.dnd.inside = false;
				}
				this.data.dnd.prepared = true;
				return this.dnd_show();
			},
			dnd_show : function () {
				if(!this.data.dnd.prepared) { return; }
				var o = ["before","inside","after"],
					r = false,
					rtl = this._get_settings().core.rtl,
					pos;
				if(this.data.dnd.w < this.data.core.li_height/3) { o = ["before","inside","after"]; }
				else if(this.data.dnd.w <= this.data.core.li_height*2/3) {
					o = this.data.dnd.w < this.data.core.li_height/2 ? ["inside","before","after"] : ["inside","after","before"];
				}
				else { o = ["after","inside","before"]; }
				$.each(o, $.proxy(function (i, val) { 
					if(this.data.dnd[val]) {
						$.vakata.dnd.helper.children("ins").attr("class","jstree-ok");
						r = val;
						return false;
					}
				}, this));
				if(r === false) { $.vakata.dnd.helper.children("ins").attr("class","jstree-invalid"); }
				
				pos = rtl ? (this.data.dnd.off.right - 18) : (this.data.dnd.off.left + 10);
				switch(r) {
					case "before":
						m.css({ "left" : pos + "px", "top" : (this.data.dnd.off.top - 6) + "px" }).show();
						break;
					case "after":
						m.css({ "left" : pos + "px", "top" : (this.data.dnd.off.top + this.data.core.li_height - 7) + "px" }).show();
						break;
					case "inside":
						m.css({ "left" : pos + ( rtl ? -4 : 4) + "px", "top" : (this.data.dnd.off.top + this.data.core.li_height/2 - 5) + "px" }).show();
						break;
					default:
						m.hide();
						break;
				}
				return r;
			},
			dnd_open : function () {
				this.data.dnd.to2 = false;
				this.open_node(r, $.proxy(this.dnd_prepare,this), true);
			},
			dnd_finish : function (e) {
				if(this.data.dnd.foreign) {
					if(this.data.dnd.after || this.data.dnd.before || this.data.dnd.inside) {
						this._get_settings().dnd.drag_finish.call(this, { "o" : o, "r" : r });
					}
				}
				else {
					this.dnd_prepare();
					this.move_node(o, r, this.dnd_show(), e[this._get_settings().dnd.copy_modifier + "Key"]);
				}
				o = false;
				r = false;
				m.hide();
			},
			dnd_enter : function (obj) {
				var s = this._get_settings().dnd;
				this.data.dnd.prepared = false;
				r = this._get_node(obj);
				if(s.check_timeout) { 
					// do the calculations after a minimal timeout (users tend to drag quickly to the desired location)
					if(this.data.dnd.to1) { clearTimeout(this.data.dnd.to1); }
					this.data.dnd.to1 = setTimeout($.proxy(this.dnd_prepare, this), s.check_timeout); 
				}
				else { 
					this.dnd_prepare(); 
				}
				if(s.open_timeout) { 
					if(this.data.dnd.to2) { clearTimeout(this.data.dnd.to2); }
					if(r && r.length && r.hasClass("jstree-closed")) { 
						// if the node is closed - open it, then recalculate
						this.data.dnd.to2 = setTimeout($.proxy(this.dnd_open, this), s.open_timeout);
					}
				}
				else {
					if(r && r.length && r.hasClass("jstree-closed")) { 
						this.dnd_open();
					}
				}
			},
			start_drag : function (obj, e) {
				o = this._get_node(obj);
				if(this.data.ui && this.is_selected(o)) { o = this._get_node(null, true); }
				$.vakata.dnd.drag_start(e, { jstree : true, obj : o }, "<ins class='jstree-icon'></ins>" + (o.length > 1 ? "Multiple selection" : this.get_text(o)) );
				if(this.data.themes) { 
					m.attr("class", "jstree-" + this.data.themes.theme); 
					$.vakata.dnd.helper.attr("class", "jstree-dnd-helper jstree-" + this.data.themes.theme); 
				}
				var cnt = this.get_container();
				this.data.dnd.cof = cnt.children("ul").offset();
				this.data.dnd.cw = parseInt(cnt.width(),10);
				this.data.dnd.ch = parseInt(cnt.height(),10);
				this.data.dnd.active = true;
			}
		}
	});
	$(function() {
		var css_string = '' + 
			'#vakata-dragged ins { display:block; text-decoration:none; width:16px; height:16px; margin:0 0 0 0; padding:0; position:absolute; top:4px; left:4px; } ' + 
			'#vakata-dragged .jstree-ok { background:green; } ' + 
			'#vakata-dragged .jstree-invalid { background:red; } ' + 
			'#jstree-marker { padding:0; margin:0; line-height:12px; font-size:1px; overflow:hidden; height:12px; width:8px; position:absolute; top:-30px; z-index:10000; background-repeat:no-repeat; display:none; background-color:silver; } ';
		$.vakata.css.add_sheet({ str : css_string });
		m = $("<div>").attr({ id : "jstree-marker" }).hide().appendTo("body");
		$(document).bind("drag_start.vakata", function (e, data) {
			if(data.data.jstree) { 
				m.show(); 
			}
		});
		$(document).bind("drag_stop.vakata", function (e, data) {
			if(data.data.jstree) { m.hide(); }
		});
	});
})(jQuery);
//*/

/*
 * jsTree checkbox plugin 1.0
 * Inserts checkboxes in front of every node
 * Depends on the ui plugin
 * DOES NOT WORK NICELY WITH MULTITREE DRAG'N'DROP
 */
(function ($) {
	$.jstree.plugin("checkbox", {
		__init : function () {
			this.select_node = this.deselect_node = this.deselect_all = $.noop;
			this.get_selected = this.get_checked;

			this.get_container()
				.bind("open_node.jstree create_node.jstree clean_node.jstree", $.proxy(function (e, data) { 
						this._prepare_checkboxes(data.rslt.obj);
					}, this))
				.bind("loaded.jstree", $.proxy(function (e) {
						this._prepare_checkboxes();
					}, this))
				.delegate("a", "click.jstree", $.proxy(function (e) {
						if(this._get_node(e.target).hasClass("jstree-checked")) { this.uncheck_node(e.target); }
						else { this.check_node(e.target); }
						if(this.data.ui) { this.save_selected(); }
						if(this.data.cookies) { this.save_cookie("select_node"); }
						e.preventDefault();
					}, this));
		},
		__destroy : function () {
			this.get_container().find(".jstree-checkbox").remove();
		},
		_fn : {
			_prepare_checkboxes : function (obj) {
				obj = !obj || obj == -1 ? this.get_container() : this._get_node(obj);
				var c, _this = this, t;
				obj.each(function () {
					t = $(this);
					c = t.is("li") && t.hasClass("jstree-checked") ? "jstree-checked" : "jstree-unchecked";
					t.find("a").not(":has(.jstree-checkbox)").prepend("<ins class='jstree-checkbox'>&#160;</ins>").parent().not(".jstree-checked, .jstree-unchecked").addClass(c);
				});
				if(obj.is("li")) { this._repair_state(obj); }
				else { obj.find("> ul > li").each(function () { _this._repair_state(this); }); }
			},
			change_state : function (obj, state) {
				obj = this._get_node(obj);
				state = (state === false || state === true) ? state : obj.hasClass("jstree-checked");
				if(state) { obj.find("li").andSelf().removeClass("jstree-checked jstree-undetermined").addClass("jstree-unchecked"); }
				else { 
					obj.find("li").andSelf().removeClass("jstree-unchecked jstree-undetermined").addClass("jstree-checked"); 
					if(this.data.ui) { this.data.ui.last_selected = obj; }
					this.data.checkbox.last_selected = obj;
				}
				obj.parentsUntil(".jstree", "li").each(function () {
					var $this = $(this);
					if(state) {
						if($this.children("ul").children(".jstree-checked, .jstree-undetermined").length) {
							$this.parentsUntil(".jstree", "li").andSelf().removeClass("jstree-checked jstree-unchecked").addClass("jstree-undetermined");
							return false;
						}
						else {
							$this.removeClass("jstree-checked jstree-undetermined").addClass("jstree-unchecked");
						}
					}
					else {
						if($this.children("ul").children(".jstree-unchecked, .jstree-undetermined").length) {
							$this.parentsUntil(".jstree", "li").andSelf().removeClass("jstree-checked jstree-unchecked").addClass("jstree-undetermined");
							return false;
						}
						else {
							$this.removeClass("jstree-unchecked jstree-undetermined").addClass("jstree-checked");
						}
					}
				});
				if(this.data.ui) { this.data.ui.selected = this.get_checked(); }
				this.__callback(obj);
			},
			check_node : function (obj) {
				this.change_state(obj, false);
			},
			uncheck_node : function (obj) {
				this.change_state(obj, true);
			},
			check_all : function () {
				var _this = this;
				this.get_container().children("ul").children("li").each(function () {
					_this.check_node(this, false);
				});
			},
			uncheck_all : function () {
				var _this = this;
				this.get_container().children("ul").children("li").each(function () {
					_this.change_state(this, true);
				});
			},

			is_checked : function(obj) {
				obj = this._get_node(obj);
				return obj.length ? obj.is(".jstree-checked") : false;
			},
			get_checked : function (obj) {
				obj = !obj || obj === -1 ? this.get_container() : this._get_node(obj);
				return obj.find("> ul > .jstree-checked, .jstree-undetermined > ul > .jstree-checked");
			},
			get_unchecked : function (obj) { 
				obj = !obj || obj === -1 ? this.get_container() : this._get_node(obj);
				return obj.find("> ul > .jstree-unchecked, .jstree-undetermined > ul > .jstree-unchecked");
			},

			show_checkboxes : function () { this.get_container().children("ul").removeClass("jstree-no-checkboxes"); },
			hide_checkboxes : function () { this.get_container().children("ul").addClass("jstree-no-checkboxes"); },

			_repair_state : function (obj) {
				obj = this._get_node(obj);
				if(!obj.length) { return; }
				var a = obj.find("> ul > .jstree-checked").length,
					b = obj.find("> ul > .jstree-undetermined").length,
					c = obj.find("> ul > li").length;

				if(c === 0) { if(obj.hasClass("jstree-undetermined")) { this.check_node(obj); } }
				else if(a === 0 && b === 0) { this.uncheck_node(obj); }
				else if(a === c) { this.check_node(obj); }
				else { 
					obj.parentsUntil(".jstree","li").removeClass("jstree-checked jstree-unchecked").addClass("jstree-undetermined");
				}
			},
			reselect : function () {
				if(this.data.ui) { 
					var _this = this,
						s = this.data.ui.to_select;
					s = $.map($.makeArray(s), function (n) { return "#" + n.toString().replace(/^#/,"").replace('\\/','/').replace('/','\\/'); });
					this.deselect_all();
					$.each(s, function (i, val) { _this.check_node(val); });
					this.__callback();
				}
			}
		}
	});
})(jQuery);
//*/

/* 
 * jsTree XML 1.0
 * The XML data store. Datastores are build by overriding the `load_node` and `_is_loaded` functions.
 */
(function ($) {
	$.vakata.xslt = function (xml, xsl, callback) {
		var rs = "", xm, xs, processor, support;
		if(document.recalc) {
			xm = document.createElement('xml');
			xs = document.createElement('xml');
			xm.innerHTML = xml;
			xs.innerHTML = xsl;
			$("body").append(xm).append(xs);
			setTimeout( (function (xm, xs, callback) {
				return function () {
					callback.call(null, xm.transformNode(xs.XMLDocument));
					setTimeout( (function (xm, xs) { return function () { jQuery("body").remove(xm).remove(xs); }; })(xm, xs), 200);
				};
			}) (xm, xs, callback), 100);
			return true;
		}
		if(typeof window.DOMParser !== "undefined" && typeof window.XMLHttpRequest !== "undefined" && typeof window.XSLTProcessor !== "undefined") {
			processor = new XSLTProcessor();
			support = $.isFunction(processor.transformDocument) ? (typeof window.XMLSerializer !== "undefined") : true;
			if(!support) { return false; }
			xml = new DOMParser().parseFromString(xml, "text/xml");
			xsl = new DOMParser().parseFromString(xsl, "text/xml");
			if($.isFunction(processor.transformDocument)) {
				rs = document.implementation.createDocument("", "", null);
				processor.transformDocument(xml, xsl, rs, null);
				callback.call(null, XMLSerializer().serializeToString(rs));
				return true;
			}
			else {
				processor.importStylesheet(xsl);
				rs = processor.transformToFragment(xml, document);
				callback.call(null, $("<div>").append(rs).html());
				return true;
			}
		}
		return false;
	};
	var xsl = {
		'nest' : '<?xml version="1.0" encoding="utf-8" ?>' + 
			'<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >' + 
			'<xsl:output method="html" encoding="utf-8" omit-xml-declaration="yes" standalone="no" indent="no" media-type="text/html" />' + 
			'<xsl:template match="/">' + 
			'	<xsl:call-template name="nodes">' + 
			'		<xsl:with-param name="node" select="/root" />' + 
			'	</xsl:call-template>' + 
			'</xsl:template>' + 
			'<xsl:template name="nodes">' + 
			'	<xsl:param name="node" />' + 
			'	<ul>' + 
			'	<xsl:for-each select="$node/item">' + 
			'		<xsl:variable name="children" select="count(./item) &gt; 0" />' + 
			'		<li>' + 
			'			<xsl:attribute name="class">' + 
			'				<xsl:if test="position() = last()">jstree-last </xsl:if>' + 
			'				<xsl:choose>' + 
			'					<xsl:when test="@state = \'open\'">jstree-open </xsl:when>' + 
			'					<xsl:when test="$children or @hasChildren or @state = \'closed\'">jstree-closed </xsl:when>' + 
			'					<xsl:otherwise>jstree-leaf </xsl:otherwise>' + 
			'				</xsl:choose>' + 
			'				<xsl:value-of select="@class" />' + 
			'			</xsl:attribute>' + 
			'			<xsl:for-each select="@*">' + 
			'				<xsl:if test="name() != \'class\' and name() != \'state\' and name() != \'hasChildren\'">' + 
			'					<xsl:attribute name="{name()}"><xsl:value-of select="." /></xsl:attribute>' + 
			'				</xsl:if>' + 
			'			</xsl:for-each>' + 
			'	<ins class="jstree-icon"><xsl:text>&#xa0;</xsl:text></ins>' + 
			'			<xsl:for-each select="content/name">' + 
			'				<a>' + 
			'				<xsl:attribute name="href">' + 
			'					<xsl:choose>' + 
			'					<xsl:when test="@href"><xsl:value-of select="@href" /></xsl:when>' + 
			'					<xsl:otherwise>#</xsl:otherwise>' + 
			'					</xsl:choose>' + 
			'				</xsl:attribute>' + 
			'				<xsl:attribute name="class"><xsl:value-of select="@lang" /> <xsl:value-of select="@class" /></xsl:attribute>' + 
			'				<xsl:attribute name="style"><xsl:value-of select="@style" /></xsl:attribute>' + 
			'				<xsl:for-each select="@*">' + 
			'					<xsl:if test="name() != \'style\' and name() != \'class\' and name() != \'href\'">' + 
			'						<xsl:attribute name="{name()}"><xsl:value-of select="." /></xsl:attribute>' + 
			'					</xsl:if>' + 
			'				</xsl:for-each>' + 
			'					<ins>' + 
			'						<xsl:attribute name="class">jstree-icon ' + 
			'							<xsl:if test="string-length(attribute::icon) > 0 and not(contains(@icon,\'/\'))"><xsl:value-of select="@icon" /></xsl:if>' + 
			'						</xsl:attribute>' + 
			'						<xsl:if test="string-length(attribute::icon) > 0 and contains(@icon,\'/\')"><xsl:attribute name="style">background:url(<xsl:value-of select="@icon" />) center center no-repeat;</xsl:attribute></xsl:if>' + 
			'						<xsl:text>&#xa0;</xsl:text>' + 
			'					</ins>' + 
			'					<xsl:value-of select="current()" />' + 
			'				</a>' + 
			'			</xsl:for-each>' + 
			'			<xsl:if test="$children or @hasChildren"><xsl:call-template name="nodes"><xsl:with-param name="node" select="current()" /></xsl:call-template></xsl:if>' + 
			'		</li>' + 
			'	</xsl:for-each>' + 
			'	</ul>' + 
			'</xsl:template>' + 
			'</xsl:stylesheet>',

		'flat' : '<?xml version="1.0" encoding="utf-8" ?>' + 
			'<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >' + 
			'<xsl:output method="html" encoding="utf-8" omit-xml-declaration="yes" standalone="no" indent="no" media-type="text/xml" />' + 
			'<xsl:template match="/">' + 
			'	<ul>' + 
			'	<xsl:for-each select="//item[not(@parent_id) or @parent_id=0 or not(@parent_id = //item/@id)]">' + /* the last `or` may be removed */
			'		<xsl:call-template name="nodes">' + 
			'			<xsl:with-param name="node" select="." />' + 
			'			<xsl:with-param name="is_last" select="number(position() = last())" />' + 
			'		</xsl:call-template>' + 
			'	</xsl:for-each>' + 
			'	</ul>' + 
			'</xsl:template>' + 
			'<xsl:template name="nodes">' + 
			'	<xsl:param name="node" />' + 
			'	<xsl:param name="is_last" />' + 
			'	<xsl:variable name="children" select="count(//item[@parent_id=$node/attribute::id]) &gt; 0" />' + 
			'	<li>' + 
			'	<xsl:attribute name="class">' + 
			'		<xsl:if test="$is_last = true()">jstree-last </xsl:if>' + 
			'		<xsl:choose>' + 
			'			<xsl:when test="@state = \'open\'">jstree-open </xsl:when>' + 
			'			<xsl:when test="$children or @hasChildren or @state = \'closed\'">jstree-closed </xsl:when>' + 
			'			<xsl:otherwise>jstree-leaf </xsl:otherwise>' + 
			'		</xsl:choose>' + 
			'		<xsl:value-of select="@class" />' + 
			'	</xsl:attribute>' + 
			'	<xsl:for-each select="@*">' + 
			'		<xsl:if test="name() != \'parent_id\' and name() != \'hasChildren\' and name() != \'class\' and name() != \'state\'">' + 
			'		<xsl:attribute name="{name()}"><xsl:value-of select="." /></xsl:attribute>' + 
			'		</xsl:if>' + 
			'	</xsl:for-each>' + 
			'	<ins class="jstree-icon"><xsl:text>&#xa0;</xsl:text></ins>' + 
			'	<xsl:for-each select="content/name">' + 
			'		<a>' + 
			'		<xsl:attribute name="href">' + 
			'			<xsl:choose>' + 
			'			<xsl:when test="@href"><xsl:value-of select="@href" /></xsl:when>' + 
			'			<xsl:otherwise>#</xsl:otherwise>' + 
			'			</xsl:choose>' + 
			'		</xsl:attribute>' + 
			'		<xsl:attribute name="class"><xsl:value-of select="@lang" /> <xsl:value-of select="@class" /></xsl:attribute>' + 
			'		<xsl:attribute name="style"><xsl:value-of select="@style" /></xsl:attribute>' + 
			'		<xsl:for-each select="@*">' + 
			'			<xsl:if test="name() != \'style\' and name() != \'class\' and name() != \'href\'">' + 
			'				<xsl:attribute name="{name()}"><xsl:value-of select="." /></xsl:attribute>' + 
			'			</xsl:if>' + 
			'		</xsl:for-each>' + 
			'			<ins>' + 
			'				<xsl:attribute name="class">jstree-icon ' + 
			'					<xsl:if test="string-length(attribute::icon) > 0 and not(contains(@icon,\'/\'))"><xsl:value-of select="@icon" /></xsl:if>' + 
			'				</xsl:attribute>' + 
			'				<xsl:if test="string-length(attribute::icon) > 0 and contains(@icon,\'/\')"><xsl:attribute name="style">background:url(<xsl:value-of select="@icon" />) center center no-repeat;</xsl:attribute></xsl:if>' + 
			'				<xsl:text>&#xa0;</xsl:text>' + 
			'			</ins>' + 
			'			<xsl:value-of select="current()" />' + 
			'		</a>' + 
			'	</xsl:for-each>' + 
			'	<xsl:if test="$children">' + 
			'		<ul>' + 
			'		<xsl:for-each select="//item[@parent_id=$node/attribute::id]">' + 
			'			<xsl:call-template name="nodes">' + 
			'				<xsl:with-param name="node" select="." />' + 
			'				<xsl:with-param name="is_last" select="number(position() = last())" />' + 
			'			</xsl:call-template>' + 
			'		</xsl:for-each>' + 
			'		</ul>' + 
			'	</xsl:if>' + 
			'	</li>' + 
			'</xsl:template>' + 
			'</xsl:stylesheet>'
	};
	$.jstree.plugin("xml_data", {
		defaults : { 
			data : false,
			ajax : false,
			xsl : "flat",
			clean_node : false,
			correct_state : true
		},
		_fn : {
			load_node : function (obj, s_call, e_call) { var _this = this; this.load_node_xml(obj, function () { _this.__callback({ "obj" : obj }); s_call.call(this); }, e_call); },
			_is_loaded : function (obj) { 
				var s = this._get_settings().xml_data;
				obj = this._get_node(obj);
				return obj == -1 || !obj || !s.ajax || obj.is(".jstree-open, .jstree-leaf") || obj.children("ul").children("li").size() > 0;
			},
			load_node_xml : function (obj, s_call, e_call) {
				var s = this.get_settings().xml_data,
					error_func = function () {},
					success_func = function () {};

				obj = this._get_node(obj);
				if(obj && obj !== -1) {
					if(obj.data("jstree-is-loading")) { return; }
					else { obj.data("jstree-is-loading",true); }
				}
				switch(!0) {
					case (!s.data && !s.ajax): throw "Neither data nor ajax settings supplied.";
					case (!!s.data && !s.ajax) || (!!s.data && !!s.ajax && (!obj || obj === -1)):
						if(!obj || obj == -1) {
							this.parse_xml(s.data, $.proxy(function (d) {
								if(d) {
									d = d.replace(/ ?xmlns="[^"]*"/ig, "");
									if(d.length > 10) {
										d = $(d);
										this.get_container().children("ul").empty().append(d.children());
										if(s.clean_node) { this.clean_node(obj); }
										if(s_call) { s_call.call(this); }
									}
								}
								else { 
									if(s.correct_state) { 
										this.get_container().children("ul").empty(); 
										if(s_call) { s_call.call(this); }
									}
								}
							}, this));
						}
						break;
					case (!s.data && !!s.ajax) || (!!s.data && !!s.ajax && obj && obj !== -1):
						error_func = function (x, t, e) {
							var ef = this.get_settings().xml_data.ajax.error; 
							if(ef) { ef.call(this, x, t, e); }
							if(obj !== -1 && obj.length) {
								obj.children(".jstree-loading").removeClass("jstree-loading");
								obj.data("jstree-is-loading",false);
								if(t === "success" && s.correct_state) { obj.removeClass("jstree-open jstree-closed").addClass("jstree-leaf"); }
							}
							else {
								if(t === "success" && s.correct_state) { this.get_container().children("ul").empty(); }
							}
							if(e_call) { e_call.call(this); }
						};
						success_func = function (d, t, x) {
							d = x.responseText;
							var sf = this.get_settings().xml_data.ajax.success; 
							if(sf) { d = sf.call(this,d,t,x) || d; }
							if(d == "") {
								return error_func.call(this, x, t, "");
							}
							this.parse_xml(d, $.proxy(function (d) {
								if(d) {
									d = d.replace(/ ?xmlns="[^"]*"/ig, "");
									if(d.length > 10) {
										d = $(d);
										if(obj === -1 || !obj) { this.get_container().children("ul").empty().append(d.children()); }
										else { obj.children(".jstree-loading").removeClass("jstree-loading"); obj.append(d); obj.data("jstree-is-loading",false); }
										if(s.clean_node) { this.clean_node(obj); }
										if(s_call) { s_call.call(this); }
									}
									else {
										if(obj && obj !== -1) { 
											obj.children(".jstree-loading").removeClass("jstree-loading");
											obj.data("jstree-is-loading",false);
											if(s.correct_state) { 
												obj.removeClass("jstree-open jstree-closed").addClass("jstree-leaf"); 
												if(s_call) { s_call.call(this); } 
											}
										}
										else {
											if(s.correct_state) { 
												this.get_container().children("ul").empty();
												if(s_call) { s_call.call(this); } 
											}
										}
									}
								}
							}, this));
						};
						s.ajax.context = this;
						s.ajax.error = error_func;
						s.ajax.success = success_func;
						if(!s.ajax.dataType) { s.ajax.dataType = "xml"; }
						if($.isFunction(s.ajax.url)) { s.ajax.url = s.ajax.url.call(this, obj); }
						if($.isFunction(s.ajax.data)) { s.ajax.data = s.ajax.data.call(this, obj); }
						$.ajax(s.ajax);
						break;
				}
			},
			parse_xml : function (xml, callback) {
				var s = this._get_settings().xml_data;
				$.vakata.xslt(xml, xsl[s.xsl], callback);
			},
			get_xml : function (tp, obj, li_attr, a_attr, is_callback) {
				var result = "", 
					s = this._get_settings(), 
					_this = this,
					tmp1, tmp2, li, a, lang;
				if(!tp) { tp = "flat"; }
				if(!is_callback) { is_callback = 0; }
				obj = this._get_node(obj);
				if(!obj || obj === -1) { obj = this.get_container().find("> ul > li"); }
				li_attr = $.isArray(li_attr) ? li_attr : [ "id", "class" ];
				if(!is_callback && this.data.types && $.inArray(s.types.type_attr, li_attr) === -1) { li_attr.push(s.types.type_attr); }

				a_attr = $.isArray(a_attr) ? a_attr : [ ];

				if(!is_callback) { result += "<root>"; }
				obj.each(function () {
					result += "<item";
					li = $(this);
					$.each(li_attr, function (i, v) { result += " " + v + "=\"" + (li.attr(v) || "").replace(/jstree[^ ]*|$/ig,'').replace(/^\s+$/ig,"") + "\""; });
					if(li.hasClass("jstree-open")) { result += " state=\"open\""; }
					if(li.hasClass("jstree-closed")) { result += " state=\"closed\""; }
					if(tp === "flat") { result += " parent_id=\"" + is_callback + "\""; }
					result += ">";
					result += "<content>";
					a = li.children("a");
					a.each(function () {
						tmp1 = $(this);
						lang = false;
						result += "<name";
						if($.inArray("languages", s.plugins) !== -1) {
							$.each(s.languages, function (k, z) {
								if(tmp1.hasClass(z)) { result += " lang=\"" + z + "\""; lang = z; return false; }
							});
						}
						if(a_attr.length) { 
							$.each(a_attr, function (k, z) {
								result += " " + z + "=\"" + (tmp1.attr(z) || "").replace(/jstree[^ ]*|$/ig,'') + "\"";
							});
						}
						if(tmp1.children("ins").get(0).className.replace(/jstree[^ ]*|$/ig,'').replace(/^\s+$/ig,"").length) {
							result += ' icon="' + tmp1.children("ins").get(0).className.replace(/jstree[^ ]*|$/ig,'').replace(/^\s+$/ig,"") + '"';
						}
						if(tmp1.children("ins").get(0).style.backgroundImage.length) {
							result += ' icon="' + tmp1.children("ins").get(0).style.backgroundImage.replace("url(","").replace(")","") + '"';
						}
						result += ">";
						result += "<![CDATA[" + _this.get_text(tmp1, lang) + "]]>";
						result += "</name>";
					});
					result += "</content>";
					tmp2 = li[0].id;
					li = li.find("> ul > li");
					if(li.length) { tmp2 = _this.get_xml(tp, li, li_attr, a_attr, tmp2); }
					else { tmp2 = ""; }
					if(tp == "nest") { result += tmp2; }
					result += "</item>";
					if(tp == "flat") { result += tmp2; }
				});
				if(!is_callback) { result += "</root>"; }
				return result;
			}
		}
	});
})(jQuery);
//*/

/*
 * jsTree search plugin 1.0
 * Enables both sync and async search on the tree
 * DOES NOT WORK WITH JSON PROGRESSIVE RENDER
 */
(function ($) {
	$.expr[':'].jstree_contains = function(a,i,m){
		return (a.textContent || a.innerText || "").toLowerCase().indexOf(m[3].toLowerCase())>=0;
	};
	$.jstree.plugin("search", {
		__init : function () {
			this.data.search.str = "";
			this.data.search.result = $();
		},
		defaults : {
			ajax : false, // OR ajax object
			case_insensitive : false
		},
		_fn : {
			search : function (str, skip_async) {
				if(str === "") { return; }
				var s = this.get_settings().search, 
					t = this,
					error_func = function () { },
					success_func = function () { };
				this.data.search.str = str;

				if(!skip_async && s.ajax !== false && this.get_container().find(".jstree-closed:eq(0)").length > 0) {
					this.search.supress_callback = true;
					error_func = function () { };
					success_func = function (d, t, x) {
						var sf = this.get_settings().search.ajax.success; 
						if(sf) { d = sf.call(this,d,t,x) || d; }
						this.data.search.to_open = d;
						this._search_open();
					};
					s.ajax.context = this;
					s.ajax.error = error_func;
					s.ajax.success = success_func;
					if($.isFunction(s.ajax.url)) { s.ajax.url = s.ajax.url.call(this, str); }
					if($.isFunction(s.ajax.data)) { s.ajax.data = s.ajax.data.call(this, str); }
					if(!s.ajax.data) { s.ajax.data = { "search_string" : str }; }
					if(!s.ajax.dataType || /^json/.exec(s.ajax.dataType)) { s.ajax.dataType = "json"; }
					$.ajax(s.ajax);
					return;
				}
				if(this.data.search.result.length) { this.clear_search(); }
				this.data.search.result = this.get_container().find("a" + (this.data.languages ? "." + this.get_lang() : "" ) + ":" + (s.case_insensitive ? "jstree_contains" : "contains") + "(" + this.data.search.str + ")");
				this.data.search.result.addClass("jstree-search").parents(".jstree-closed").each(function () {
					t.open_node(this, false, true);
				});
				this.__callback({ nodes : this.data.search.result, str : str });
			},
			clear_search : function (str) {
				this.data.search.result.removeClass("jstree-search");
				this.__callback(this.data.search.result);
				this.data.search.result = $();
			},
			_search_open : function (is_callback) {
				var _this = this,
					done = true,
					current = [],
					remaining = [];
				if(this.data.search.to_open.length) {
					$.each(this.data.search.to_open, function (i, val) {
						if(val == "#") { return true; }
						if($(val).length && $(val).is(".jstree-closed")) { current.push(val); }
						else { remaining.push(val); }
					});
					if(current.length) {
						this.data.search.to_open = remaining;
						$.each(current, function (i, val) { 
							_this.open_node(val, function () { _this._search_open(true); }); 
						});
						done = false;
					}
				}
				if(done) { this.search(this.data.search.str, true); }
			}
		}
	});
})(jQuery);
//*/

/*
 * jsTree contextmenu plugin 1.0
 */
(function ($) {
	$.vakata.context = {
		cnt		: $("<div id='vakata-contextmenu'>"),
		vis		: false,
		tgt		: false,
		par		: false,
		func	: false,
		data	: false,
		show	: function (s, t, x, y, d, p) {
			var html = $.vakata.context.parse(s), h, w;
			if(!html) { return; }
			$.vakata.context.vis = true;
			$.vakata.context.tgt = t;
			$.vakata.context.par = p || t || null;
			$.vakata.context.data = d || null;
			$.vakata.context.cnt
				.html(html)
				.css({ "visibility" : "hidden", "display" : "block", "left" : 0, "top" : 0 });
			h = $.vakata.context.cnt.height();
			w = $.vakata.context.cnt.width();
			if(x + w > $(document).width()) { 
				x = $(document).width() - (w + 5); 
				$.vakata.context.cnt.find("li > ul").addClass("right"); 
			}
			if(y + h > $(document).height()) { 
				y = y - (h + t[0].offsetHeight); 
				$.vakata.context.cnt.find("li > ul").addClass("bottom"); 
			}

			$.vakata.context.cnt
				.css({ "left" : x, "top" : y })
				.find("li:has(ul)")
					.bind("mouseenter", function (e) { 
						var w = $(document).width(),
							h = $(document).height(),
							ul = $(this).children("ul").show(); 
						if(w !== $(document).width()) { ul.toggleClass("right"); }
						if(h !== $(document).height()) { ul.toggleClass("bottom"); }
					})
					.bind("mouseleave", function (e) { 
						$(this).children("ul").hide(); 
					})
					.end()
				.css({ "visibility" : "visible" })
				.show();
			$(document).triggerHandler("context_show.vakata");
		},
		hide	: function () {
			$.vakata.context.vis = false;
			$.vakata.context.cnt.attr("class","").hide();
			$(document).triggerHandler("context_hide.vakata");
		},
		parse	: function (s, is_callback) {
			if(!s) { return false; }
			var str = "",
				tmp = false,
				was_sep = true;
			if(!is_callback) { $.vakata.context.func = {}; }
			str += "<ul>";
			$.each(s, function (i, val) {
				if(!val) { return true; }
				$.vakata.context.func[i] = val.action;
				if(!was_sep && val.separator_before) {
					str += "<li class='vakata-separator vakata-separator-before'></li>";
				}
				was_sep = false;
				str += "<li class='" + (val._class || "") + (val._disabled ? " jstree-contextmenu-disabled " : "") + "'><ins ";
				if(val.icon && val.icon.indexOf("/") === -1) { str += " class='" + val.icon + "' "; }
				if(val.icon && val.icon.indexOf("/") !== -1) { str += " style='background:url(" + val.icon + ") center center no-repeat;' "; }
				str += ">&#160;</ins><a href='#' rel='" + i + "'>";
				if(val.submenu) {
					str += "<span style='float:right;'>&raquo;</span>";
				}
				str += val.label + "</a>";
				if(val.submenu) {
					tmp = $.vakata.context.parse(val.submenu, true);
					if(tmp) { str += tmp; }
				}
				str += "</li>";
				if(val.separator_after) {
					str += "<li class='vakata-separator vakata-separator-after'></li>";
					was_sep = true;
				}
			});
			str = str.replace(/<li class\='vakata-separator vakata-separator-after'\><\/li\>$/,"");
			str += "</ul>";
			return str.length > 10 ? str : false;
		},
		exec	: function (i) {
			if($.isFunction($.vakata.context.func[i])) {
				$.vakata.context.func[i].call($.vakata.context.data, $.vakata.context.par);
				return true;
			}
			else { return false; }
		}
	};
	$(function () {
		var css_string = '' + 
			'#vakata-contextmenu { display:none; position:absolute; margin:0; padding:0; min-width:180px; background:#ebebeb; border:1px solid silver; z-index:10000; *width:180px; } ' + 
			'#vakata-contextmenu ul { min-width:180px; *width:180px; } ' + 
			'#vakata-contextmenu ul, #vakata-contextmenu li { margin:0; padding:0; list-style-type:none; display:block; } ' + 
			'#vakata-contextmenu li { line-height:20px; min-height:20px; position:relative; padding:0px; } ' + 
			'#vakata-contextmenu li a { padding:1px 6px; line-height:17px; display:block; text-decoration:none; margin:1px 1px 0 1px; } ' + 
			'#vakata-contextmenu li ins { float:left; width:16px; height:16px; text-decoration:none; margin-right:2px; } ' + 
			'#vakata-contextmenu li a:hover, #vakata-contextmenu li.vakata-hover > a { background:gray; color:white; } ' + 
			'#vakata-contextmenu li ul { display:none; position:absolute; top:-2px; left:100%; background:#ebebeb; border:1px solid gray; } ' + 
			'#vakata-contextmenu .right { right:100%; left:auto; } ' + 
			'#vakata-contextmenu .bottom { bottom:-1px; top:auto; } ' + 
			'#vakata-contextmenu li.vakata-separator { min-height:0; height:1px; line-height:1px; font-size:1px; overflow:hidden; margin:0 2px; background:silver; /* border-top:1px solid #fefefe; */ padding:0; } ';
		$.vakata.css.add_sheet({ str : css_string });
		$.vakata.context.cnt
			.delegate("a","click", function (e) { e.preventDefault(); })
			.delegate("a","mouseup", function (e) {
				if(!$(this).parent().hasClass("jstree-contextmenu-disabled") && $.vakata.context.exec($(this).attr("rel"))) {
					$.vakata.context.hide();
				}
				else { $(this).blur(); }
			})
			.delegate("a","mouseover", function () {
				$.vakata.context.cnt.find(".vakata-hover").removeClass("vakata-hover");
			})
			.appendTo("body");
		$(document).bind("mousedown", function (e) { if($.vakata.context.vis && !$.contains($.vakata.context.cnt[0], e.target)) { $.vakata.context.hide(); } });
		if(typeof $.hotkeys !== "undefined") {
			$(document)
				.bind("keydown", "up", function (e) { 
					if($.vakata.context.vis) { 
						var o = $.vakata.context.cnt.find("ul:visible").last().children(".vakata-hover").removeClass("vakata-hover").prevAll("li:not(.vakata-separator)").first();
						if(!o.length) { o = $.vakata.context.cnt.find("ul:visible").last().children("li:not(.vakata-separator)").last(); }
						o.addClass("vakata-hover");
						e.stopImmediatePropagation(); 
						e.preventDefault();
					} 
				})
				.bind("keydown", "down", function (e) { 
					if($.vakata.context.vis) { 
						var o = $.vakata.context.cnt.find("ul:visible").last().children(".vakata-hover").removeClass("vakata-hover").nextAll("li:not(.vakata-separator)").first();
						if(!o.length) { o = $.vakata.context.cnt.find("ul:visible").last().children("li:not(.vakata-separator)").first(); }
						o.addClass("vakata-hover");
						e.stopImmediatePropagation(); 
						e.preventDefault();
					} 
				})
				.bind("keydown", "right", function (e) { 
					if($.vakata.context.vis) { 
						$.vakata.context.cnt.find(".vakata-hover").children("ul").show().children("li:not(.vakata-separator)").removeClass("vakata-hover").first().addClass("vakata-hover");
						e.stopImmediatePropagation(); 
						e.preventDefault();
					} 
				})
				.bind("keydown", "left", function (e) { 
					if($.vakata.context.vis) { 
						$.vakata.context.cnt.find(".vakata-hover").children("ul").hide().children(".vakata-separator").removeClass("vakata-hover");
						e.stopImmediatePropagation(); 
						e.preventDefault();
					} 
				})
				.bind("keydown", "esc", function (e) { 
					$.vakata.context.hide(); 
					e.preventDefault();
				})
				.bind("keydown", "space", function (e) { 
					$.vakata.context.cnt.find(".vakata-hover").last().children("a").click();
					e.preventDefault();
				});
		}
	});

	$.jstree.plugin("contextmenu", {
		__init : function () {
			this.get_container()
				.delegate("a", "contextmenu.jstree", $.proxy(function (e) {
						e.preventDefault();
						this.show_contextmenu(e.currentTarget, e.pageX, e.pageY);
					}, this))
				.bind("destroy.jstree", $.proxy(function () {
						if(this.data.contextmenu) {
							$.vakata.context.hide();
						}
					}, this));
			$(document).bind("context_hide.vakata", $.proxy(function () { this.data.contextmenu = false; }, this));
		},
		defaults : { 
			select_node : false, // requires UI plugin
			show_at_node : true,
			items : { // Could be a function that should return an object like this one
				"create" : {
					"separator_before"	: false,
					"separator_after"	: true,
					"label"				: "Create",
					"action"			: function (obj) { this.create(obj); }
				},
				"rename" : {
					"separator_before"	: false,
					"separator_after"	: false,
					"label"				: "Rename",
					"action"			: function (obj) { this.rename(obj); }
				},
				"remove" : {
					"separator_before"	: false,
					"icon"				: false,
					"separator_after"	: false,
					"label"				: "Delete",
					"action"			: function (obj) { this.remove(obj); }
				},
				"ccp" : {
					"separator_before"	: true,
					"icon"				: false,
					"separator_after"	: false,
					"label"				: "Edit",
					"action"			: false,
					"submenu" : { 
						"cut" : {
							"separator_before"	: false,
							"separator_after"	: false,
							"label"				: "Cut",
							"action"			: function (obj) { this.cut(obj); }
						},
						"copy" : {
							"separator_before"	: false,
							"icon"				: false,
							"separator_after"	: false,
							"label"				: "Copy",
							"action"			: function (obj) { this.copy(obj); }
						},
						"paste" : {
							"separator_before"	: false,
							"icon"				: false,
							"separator_after"	: false,
							"label"				: "Paste",
							"action"			: function (obj) { this.paste(obj); }
						}
					}
				}
			}
		},
		_fn : {
			show_contextmenu : function (obj, x, y) {
				obj = this._get_node(obj);
				var s = this.get_settings().contextmenu,
					a = obj.children("a:visible:eq(0)"),
					o = false;
				if(s.select_node && this.data.ui && !this.is_selected(obj)) {
					this.deselect_all();
					this.select_node(obj, true);
				}
				if(s.show_at_node || typeof x === "undefined" || typeof y === "undefined") {
					o = a.offset();
					x = o.left;
					y = o.top + this.data.core.li_height;
				}
				if($.isFunction(s.items)) { s.items = s.items.call(this, obj); }
				this.data.contextmenu = true;
				$.vakata.context.show(s.items, a, x, y, this, obj);
				if(this.data.themes) { $.vakata.context.cnt.attr("class", "jstree-" + this.data.themes.theme + "-context"); }
			}
		}
	});
})(jQuery);
//*/

/* 
 * jsTree types plugin 1.0
 * Adds support types of nodes
 * You can set an attribute on each li node, that represents its type.
 * According to the type setting the node may get custom icon/validation rules
 */
(function ($) {
	$.jstree.plugin("types", {
		__init : function () {
			var s = this._get_settings().types;
			this.data.types.attach_to = [];
			this.get_container()
				.bind("init.jstree", $.proxy(function () { 
						var types = s.types, 
							attr  = s.type_attr, 
							icons_css = "", 
							_this = this;

						$.each(types, function (i, tp) {
							$.each(tp, function (k, v) { 
								if(!/^(max_depth|max_children|icon|valid_children)$/.test(k)) { _this.data.types.attach_to.push(k); }
							});
							if(!tp.icon) { return true; }
							if( tp.icon.image || tp.icon.position) {
								if(i == "default")	{ icons_css += '.jstree-' + _this.get_index() + ' a > .jstree-icon { '; }
								else				{ icons_css += '.jstree-' + _this.get_index() + ' li[' + attr + '=' + i + '] > a > .jstree-icon { '; }
								if(tp.icon.image)	{ icons_css += ' background-image:url(' + tp.icon.image + '); '; }
								if(tp.icon.position){ icons_css += ' background-position:' + tp.icon.position + '; '; }
								else				{ icons_css += ' background-position:0 0; '; }
								icons_css += '} ';
							}
						});
						if(icons_css != "") { $.vakata.css.add_sheet({ 'str' : icons_css }); }
					}, this))
				.bind("before.jstree", $.proxy(function (e, data) { 
						if($.inArray(data.func, this.data.types.attach_to) !== -1) {
							var s = this._get_settings().types.types,
								t = this._get_type(data.args[0]);
							if(
								( 
									(s[t] && typeof s[t][data.func] !== "undefined") || 
									(s["default"] && typeof s["default"][data.func] !== "undefined")
								) && !this._check(data.func, data.args[0])
							) {
								e.stopImmediatePropagation();
								return false;
							}
						}
					}, this));
		},
		defaults : {
			// defines maximum number of root nodes (-1 means unlimited, -2 means disable max_children checking)
			max_children		: -1,
			// defines the maximum depth of the tree (-1 means unlimited, -2 means disable max_depth checking)
			max_depth			: -1,
			// defines valid node types for the root nodes
			valid_children		: "all",

			// where is the type stores (the rel attribute of the LI element)
			type_attr : "rel",
			// a list of types
			types : {
				// the default type
				"default" : {
					"max_children"	: -1,
					"max_depth"		: -1,
					"valid_children": "all"

					// Bound functions - you can bind any other function here (using boolean or function)
					//"select_node"	: true,
					//"open_node"	: true,
					//"close_node"	: true,
					//"create_node"	: true,
					//"delete_node"	: true
				}
			}
		},
		_fn : {
			_get_type : function (obj) {
				obj = this._get_node(obj);
				return (!obj || !obj.length) ? false : obj.attr(this._get_settings().types.type_attr) || "default";
			},
			set_type : function (str, obj) {
				obj = this._get_node(obj);
				return (!obj.length || !str) ? false : obj.attr(this._get_settings().types.type_attr, str);
			},
			_check : function (rule, obj, opts) {
				var v = false, t = this._get_type(obj), d = 0, _this = this, s = this._get_settings().types;
				if(obj === -1) { 
					if(!!s[rule]) { v = s[rule]; }
					else { return; }
				}
				else {
					if(t === false) { return; }
					if(!!s.types[t] && !!s.types[t][rule]) { v = s.types[t][rule]; }
					else if(!!s.types["default"] && !!s.types["default"][rule]) { v = s.types["default"][rule]; }
				}
				if($.isFunction(v)) { v = v.call(this, obj); }
				if(rule === "max_depth" && obj !== -1 && opts !== false && s.max_depth !== -2 && v !== 0) {
					// also include the node itself - otherwise if root node it is not checked
					this._get_node(obj).children("a:eq(0)").parentsUntil(".jstree","li").each(function (i) {
						// check if current depth already exceeds global tree depth
						if(s.max_depth !== -1 && s.max_depth - (i + 1) <= 0) { v = 0; return false; }
						d = (i === 0) ? v : _this._check(rule, this, false);
						// check if current node max depth is already matched or exceeded
						if(d !== -1 && d - (i + 1) <= 0) { v = 0; return false; }
						// otherwise - set the max depth to the current value minus current depth
						if(d >= 0 && (d - (i + 1) < v || v < 0) ) { v = d - (i + 1); }
						// if the global tree depth exists and it minus the nodes calculated so far is less than `v` or `v` is unlimited
						if(s.max_depth >= 0 && (s.max_depth - (i + 1) < v || v < 0) ) { v = s.max_depth - (i + 1); }
					});
				}
				return v;
			},
			check_move : function () {
				if(!this.__call_old()) { return false; }
				var m  = this._get_move(),
					s  = m.rt._get_settings().types,
					mc = m.rt._check("max_children", m.cr),
					md = m.rt._check("max_depth", m.cr),
					vc = m.rt._check("valid_children", m.cr),
					ch = 0, d = 1, t;

				if(vc === "none") { return false; } 
				if($.isArray(vc) && m.ot && m.ot._get_type) {
					m.o.each(function () {
						if($.inArray(m.ot._get_type(this), vc) === -1) { d = false; return false; }
					});
					if(d === false) { return false; }
				}
				if(s.max_children !== -2 && mc !== -1) {
					ch = m.cr === -1 ? this.get_container().children("> ul > li").not(m.o).length : m.cr.children("> ul > li").not(m.o).length;
					if(ch + m.o.length > mc) { return false; }
				}
				if(s.max_depth !== -2 && md !== -1) {
					d = 0;
					if(md === 0) { return false; }
					if(typeof m.o.d === "undefined") {
						// TODO: deal with progressive rendering and async when checking max_depth (how to know the depth of the moved node)
						t = m.o;
						while(t.length > 0) {
							t = t.find("> ul > li");
							d ++;
						}
						m.o.d = d;
					}
					if(md - m.o.d < 0) { return false; }
				}
				return true;
			},
			create_node : function (obj, position, js, callback, is_loaded, skip_check) {
				if(!skip_check && (is_loaded || this._is_loaded(obj))) {
					var p  = (position && position.match(/^before|after$/i) && obj !== -1) ? this._get_parent(obj) : this._get_node(obj),
						s  = this._get_settings().types,
						mc = this._check("max_children", p),
						md = this._check("max_depth", p),
						vc = this._check("valid_children", p),
						ch;
					if(!js) { js = {}; }
					if(vc === "none") { return false; } 
					if($.isArray(vc)) {
						if(!js.attr || !js.attr[s.type_attr]) { 
							if(!js.attr) { js.attr = {}; }
							js.attr[s.type_attr] = vc[0]; 
						}
						else {
							if($.inArray(js.attr[s.type_attr], vc) === -1) { return false; }
						}
					}
					if(s.max_children !== -2 && mc !== -1) {
						ch = p === -1 ? this.get_container().children("> ul > li").length : p.children("> ul > li").length;
						if(ch + 1 > mc) { return false; }
					}
					if(s.max_depth !== -2 && md !== -1 && (md - 1) < 0) { return false; }
				}
				return this.__call_old(true, obj, position, js, callback, is_loaded, skip_check);
			}
		}
	});
})(jQuery);
//*/

/* 
 * jsTree HTML data 1.0
 * The HTML data store. Datastores are build by replacing the `load_node` and `_is_loaded` functions.
 */
(function ($) {
	$.jstree.plugin("html_data", {
		__init : function () { 
			// this used to use html() and clean the whitespace, but this way any attached data was lost
			this.data.html_data.original_container_html = this.get_container().find(" > ul > li").clone(true);
			// remove white space from LI node - otherwise nodes appear a bit to the right
			this.data.html_data.original_container_html.find("li").andSelf().contents().filter(function() { return this.nodeType == 3; }).remove();
		},
		defaults : { 
			data : false,
			ajax : false,
			correct_state : true
		},
		_fn : {
			load_node : function (obj, s_call, e_call) { var _this = this; this.load_node_html(obj, function () { _this.__callback({ "obj" : obj }); s_call.call(this); }, e_call); },
			_is_loaded : function (obj) { 
				obj = this._get_node(obj); 
				return obj == -1 || !obj || !this._get_settings().html_data.ajax || obj.is(".jstree-open, .jstree-leaf") || obj.children("ul").children("li").size() > 0;
			},
			load_node_html : function (obj, s_call, e_call) {
				var d,
					s = this.get_settings().html_data,
					error_func = function () {},
					success_func = function () {};
				obj = this._get_node(obj);
				if(obj && obj !== -1) {
					if(obj.data("jstree-is-loading")) { return; }
					else { obj.data("jstree-is-loading",true); }
				}
				switch(!0) {
					case (!s.data && !s.ajax):
						if(!obj || obj == -1) {
							this.get_container()
								.children("ul").empty()
								.append(this.data.html_data.original_container_html)
								.find("li, a").filter(function () { return this.firstChild.tagName !== "INS"; }).prepend("<ins class='jstree-icon'>&#160;</ins>").end()
								.filter("a").children("ins:first-child").not(".jstree-icon").addClass("jstree-icon");
							this.clean_node();
						}
						if(s_call) { s_call.call(this); }
						break;
					case (!!s.data && !s.ajax) || (!!s.data && !!s.ajax && (!obj || obj === -1)):
						if(!obj || obj == -1) {
							d = $(s.data);
							if(!d.is("ul")) { d = $("<ul>").append(d); }
							this.get_container()
								.children("ul").empty().append(d.children())
								.find("li, a").filter(function () { return this.firstChild.tagName !== "INS"; }).prepend("<ins class='jstree-icon'>&#160;</ins>").end()
								.filter("a").children("ins:first-child").not(".jstree-icon").addClass("jstree-icon");
							this.clean_node();
						}
						if(s_call) { s_call.call(this); }
						break;
					case (!s.data && !!s.ajax) || (!!s.data && !!s.ajax && obj && obj !== -1):
						obj = this._get_node(obj);
						error_func = function (x, t, e) {
							var ef = this.get_settings().html_data.ajax.error; 
							if(ef) { ef.call(this, x, t, e); }
							if(obj != -1 && obj.length) {
								obj.children(".jstree-loading").removeClass("jstree-loading");
								obj.data("jstree-is-loading",false);
								if(t === "success" && s.correct_state) { obj.removeClass("jstree-open jstree-closed").addClass("jstree-leaf"); }
							}
							else {
								if(t === "success" && s.correct_state) { this.get_container().children("ul").empty(); }
							}
							if(e_call) { e_call.call(this); }
						};
						success_func = function (d, t, x) {
							var sf = this.get_settings().html_data.ajax.success; 
							if(sf) { d = sf.call(this,d,t,x) || d; }
							if(d == "") {
								return error_func.call(this, x, t, "");
							}
							if(d) {
								d = $(d);
								if(!d.is("ul")) { d = $("<ul>").append(d); }
								if(obj == -1 || !obj) { this.get_container().children("ul").empty().append(d.children()).find("li, a").filter(function () { return this.firstChild.tagName !== "INS"; }).prepend("<ins class='jstree-icon'>&#160;</ins>").end().filter("a").children("ins:first-child").not(".jstree-icon").addClass("jstree-icon"); }
								else { obj.children(".jstree-loading").removeClass("jstree-loading"); obj.append(d).find("li, a").filter(function () { return this.firstChild.tagName !== "INS"; }).prepend("<ins class='jstree-icon'>&#160;</ins>").end().filter("a").children("ins:first-child").not(".jstree-icon").addClass("jstree-icon"); obj.data("jstree-is-loading",false); }
								this.clean_node(obj);
								if(s_call) { s_call.call(this); }
							}
							else {
								if(obj && obj !== -1) {
									obj.children(".jstree-loading").removeClass("jstree-loading");
									obj.data("jstree-is-loading",false);
									if(s.correct_state) { 
										obj.removeClass("jstree-open jstree-closed").addClass("jstree-leaf"); 
										if(s_call) { s_call.call(this); } 
									}
								}
								else {
									if(s.correct_state) { 
										this.get_container().children("ul").empty();
										if(s_call) { s_call.call(this); } 
									}
								}
							}
						};
						s.ajax.context = this;
						s.ajax.error = error_func;
						s.ajax.success = success_func;
						if(!s.ajax.dataType) { s.ajax.dataType = "html"; }
						if($.isFunction(s.ajax.url)) { s.ajax.url = s.ajax.url.call(this, obj); }
						if($.isFunction(s.ajax.data)) { s.ajax.data = s.ajax.data.call(this, obj); }
						$.ajax(s.ajax);
						break;
				}
			}
		}
	});
	// include the HTML data plugin by default
	$.jstree.defaults.plugins.push("html_data");
})(jQuery);
//*/

/* 
 * jsTree themeroller plugin 1.0
 * Adds support for jQuery UI themes. Include this at the end of your plugins list, also make sure "themes" is not included.
 */
(function ($) {
	$.jstree.plugin("themeroller", {
		__init : function () {
			var s = this._get_settings().themeroller;
			this.get_container()
				.addClass("ui-widget-content")
				.delegate("a","mouseenter.jstree", function () {
					$(this).addClass(s.item_h);
				})
				.delegate("a","mouseleave.jstree", function () {
					$(this).removeClass(s.item_h);
				})
				.bind("open_node.jstree create_node.jstree", $.proxy(function (e, data) { 
						this._themeroller(data.rslt.obj);
					}, this))
				.bind("loaded.jstree refresh.jstree", $.proxy(function (e) {
						this._themeroller();
					}, this))
				.bind("close_node.jstree", $.proxy(function (e, data) {
						data.rslt.obj.children("ins").removeClass(s.opened).addClass(s.closed);
					}, this))
				.bind("select_node.jstree", $.proxy(function (e, data) {
						data.rslt.obj.children("a").addClass(s.item_a);
					}, this))
				.bind("deselect_node.jstree deselect_all.jstree", $.proxy(function (e, data) {
						this.get_container()
							.find("." + s.item_a).removeClass(s.item_a).end()
							.find(".jstree-clicked").addClass(s.item_a);
					}, this))
				.bind("move_node.jstree", $.proxy(function (e, data) {
						this._themeroller(data.rslt.o);
					}, this));
		},
		__destroy : function () {
			var s = this._get_settings().themeroller,
				c = [ "ui-icon" ];
			$.each(s, function (i, v) {
				v = v.split(" ");
				if(v.length) { c = c.concat(v); }
			});
			this.get_container()
				.removeClass("ui-widget-content")
				.find("." + c.join(", .")).removeClass(c.join(" "));
		},
		_fn : {
			_themeroller : function (obj) {
				var s = this._get_settings().themeroller;
				obj = !obj || obj == -1 ? this.get_container() : this._get_node(obj).parent();
				obj
					.find("li.jstree-closed > ins.jstree-icon").removeClass(s.opened).addClass("ui-icon " + s.closed).end()
					.find("li.jstree-open > ins.jstree-icon").removeClass(s.closed).addClass("ui-icon " + s.opened).end()
					.find("a").addClass(s.item)
						.children("ins.jstree-icon").addClass("ui-icon " + s.item_icon);
			}
		},
		defaults : {
			"opened" : "ui-icon-triangle-1-se",
			"closed" : "ui-icon-triangle-1-e",
			"item" : "ui-state-default",
			"item_h" : "ui-state-hover",
			"item_a" : "ui-state-active",
			"item_icon" : "ui-icon-folder-collapsed"
		}
	});
	$(function() {
		var css_string = '.jstree .ui-icon { overflow:visible; } .jstree a { padding:0 2px; }';
		$.vakata.css.add_sheet({ str : css_string });
	});
})(jQuery);
//*/

/* 
 * jsTree unique plugin 1.0
 * Forces different names amongst siblings (still a bit experimental)
 * NOTE: does not check language versions (it will not be possible to have nodes with the same title, even in different languages)
 */
(function ($) {
	$.jstree.plugin("unique", {
		__init : function () {
			this.get_container()
				.bind("before.jstree", $.proxy(function (e, data) { 
						var nms = [], res = true, p, t;
						if(data.func == "move_node") {
							// obj, ref, position, is_copy, is_prepared, skip_check
							if(data.args[4] === true) {
								if(data.args[0].o && data.args[0].o.length) {
									data.args[0].o.children("a").each(function () { nms.push($(this).text().replace(/^\s+/g,"")); });
									res = this._check_unique(nms, data.args[0].np.find("> ul > li").not(data.args[0].o));
								}
							}
						}
						if(data.func == "create_node") {
							// obj, position, js, callback, is_loaded
							if(data.args[4] || this._is_loaded(data.args[0])) {
								p = this._get_node(data.args[0]);
								if(data.args[1] && (data.args[1] === "before" || data.args[1] === "after")) {
									p = this._get_parent(data.args[0]);
									if(!p || p === -1) { p = this.get_container(); }
								}
								if(typeof data.args[2] === "string") { nms.push(data.args[2]); }
								else if(!data.args[2] || !data.args[2].data) { nms.push(this._get_settings().core.strings.new_node); }
								else { nms.push(data.args[2].data); }
								res = this._check_unique(nms, p.find("> ul > li"));
							}
						}
						if(data.func == "rename_node") {
							// obj, val
							nms.push(data.args[1]);
							t = this._get_node(data.args[0]);
							p = this._get_parent(t);
							if(!p || p === -1) { p = this.get_container(); }
							res = this._check_unique(nms, p.find("> ul > li").not(t));
						}
						if(!res) {
							e.stopPropagation();
							return false;
						}
					}, this));
		},
		_fn : { 
			_check_unique : function (nms, p) {
				var cnms = [];
				p.children("a").each(function () { cnms.push($(this).text().replace(/^\s+/g,"")); });
				if(!cnms.length || !nms.length) { return true; }
				cnms = cnms.sort().join(",,").replace(/(,|^)([^,]+)(,,\2)+(,|$)/g,"$1$2$4").replace(/,,+/g,",").replace(/,$/,"").split(",");
				if((cnms.length + nms.length) != cnms.concat(nms).sort().join(",,").replace(/(,|^)([^,]+)(,,\2)+(,|$)/g,"$1$2$4").replace(/,,+/g,",").replace(/,$/,"").split(",").length) {
					return false;
				}
				return true;
			},
			check_move : function () {
				if(!this.__call_old()) { return false; }
				var p = this._get_move(), nms = [];
				if(p.o && p.o.length) {
					p.o.children("a").each(function () { nms.push($(this).text().replace(/^\s+/g,"")); });
					return this._check_unique(nms, p.np.find("> ul > li").not(p.o));
				}
				return true;
			}
		}
	});
})(jQuery);
//*/

console.log('=============== >  jquery.jstree.js ');

/*
 * Copyright (c) 2011 Tom Coote (http://www.tomcoote.co.uk)
 * This is licensed under GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

/*jshint curly: true, eqeqeq: true, undef: true, devel: true, browser: true */
/*global jQuery */
 
/**
 * Turn a text box into an auto suggest box which search's and 
 * displays results specified in a JSON string
 * 
 *
 * @name jsonSuggest
 * @type jQuery plugin
 * @author Tom Coote (tomcoote.co.uk)
 * @version 2.0.2
 * @copyright Copyright 2011 Tom Coote
 * @license released under the GPL licences
 * 
 * @param settings <object>;
 *			url :				[default ''] A URL that will return a JSON response. Called via $.getJSON, it is passed a 
 * 								 data dictionary containing the user typed search phrase. It must return a JSON string that
 *								 represents the array of results to display.
 *			data :				[default []]An array or JSON string representation of an array of data to search through.
 *								 Example of the array format is as follows:
									[
										{
											id: 1,
											text: 'Thomas',
											image: 'img/avator1.jpg',	// optional
											extra: 'www.thomas.com'	// optional
										},
										{
											id: 2,
											text: 'Frederic',
											image: 'img/avator2.jpg',	// optional
											extra: 'www.freddy.com'	// optional
										},
										{
											id: 2,
											text: 'James',
											image: 'img/avator2.jpg',	// optional
											extra: 'www.james.com'	// optional
										}
									]
 *			minCharacters :		[default 1] Number of characters that the input should accept before running a search.
 *			maxResults :		[default undefined] If set then no more results than this number will be found.
 *			wildCard :			[default ''] A character to be used as a match all wildcard when searching. Leaving empty 
 *								 will mean results are matched inside strings but if a wildCard is present then results are 
 *								 matched from the beginning of strings.
 *			caseSensitive :		[default false] True if the filter search's are to be case sensitive.
 *			notCharacter :		[default !] The character to use at the start of any search text to specify that the results 
 *								 should NOT contain the following text.
 *			maxHeight :			[default 350] This is the maximum height that the results box can reach before scroll bars 
 *								 are shown instead of getting taller.	
 *			width:				[default undefined] If set this will become the width of the results box else the box will be 
 *								 the same width as the input.
 *			highlightMatches : 	[default true] This will add strong tags around the text that matches the search text in each result.
 *			onSelect :			[default undefined] Function that gets called once a result has been selected, gets passed in 
 *								 the object version of the result as specified in the JSON data.
 *			
 */

(function($) {

	$.fn.jsonSuggest = function(settings) {
		var defaults = {  
				url: '',
				data: [],
				minCharacters: 1,
				maxResults: undefined,
				wildCard: '',
				caseSensitive: false,
				notCharacter: '!',
				maxHeight: 350,
				highlightMatches: true,
				onSelect: undefined,
				width: undefined
			},
			getJSONTimeout;
		settings = $.extend(defaults, settings);  
	
		return this.each(function() {
			/**
			* Escape some text so that it can be used inside a regular expression
			* without implying regular expression rules iself. 
			*/
			function regexEscape(txt, omit) {
				var specials = ['/', '.', '*', '+', '?', '|',
								'(', ')', '[', ']', '{', '}', '\\'];
				
				if (omit) {
					for (var i = 0; i < specials.length; i++) {
						if (specials[i] === omit) { specials.splice(i,1); }
					}
				}
				
				var escapePatt = new RegExp('(\\' + specials.join('|\\') + ')', 'g');
				return txt.replace(escapePatt, '\\$1');
			}
			
			var obj = $(this),
				wildCardPatt = new RegExp(regexEscape(settings.wildCard || ''),'g'),
				results = $('<ul />'),
				currentSelection, pageX, pageY;
			
			/**
			* When an item has been selected then update the input box,
			* hide the results again and if set, call the onSelect function.
			*/
			function selectResultItem(item) {
				obj.val(item.text);
				$(results).html('').hide();
				
				if (typeof settings.onSelect === 'function') {
					settings.onSelect(item);
				}
			}

			/**
			* Used to get rid of the hover class on all result item elements in the
			* current set of results and add it only to the given element. We also
			* need to set the current selection to the given element here.
			*/
			function setHoverClass(el) {
				$('li a', results).removeClass('ui-state-hover');
				if (el) {
					$('a', el).addClass('ui-state-hover');
				}
				
				currentSelection = el;
			}
			
			/**
			* Build the results HTML based on an array of objects that matched
			* the search criteria, highlight the matches if that feature is turned 
			* on in the settings.
			*/
			function buildResults(resultObjects, filterTxt) {
				filterTxt = '(' + filterTxt + ')';
			
				var bOddRow = true, i, iFound = 0,
					filterPatt = settings.caseSensitive ? new RegExp(filterTxt, 'g') : new RegExp(filterTxt, 'ig');
					
				$(results).html('').hide();
				
				for (i = 0; i < resultObjects.length; i += 1) {
					var item = $('<li />'),
						text = resultObjects[i].text;
						
					if (settings.highlightMatches === true) {
						text = text.replace(filterPatt, '<strong>$1</strong>');
					}
					
					$(item).append('<a class="ui-corner-all">' + text + '</a>');
					
					if (typeof resultObjects[i].image === 'string') {
						$('>a', item).prepend('<img src="' + resultObjects[i].image + '" />');
					}
					
					if (typeof resultObjects[i].extra === 'string') {
						$('>a', item).append('<small>' + resultObjects[i].extra + '</small>');
					}
					
					$(item).addClass('ui-menu-item').
						addClass((bOddRow) ? 'odd' : 'even').
						attr('role', 'menuitem').
						click((function(n) { return function() {
							selectResultItem(resultObjects[n]);						
						};})(i)).
						mouseover((function(el) { return function() { 
							setHoverClass(el); 
						};})(item));
					
					$(results).append(item);
					
					bOddRow = !bOddRow;
					
					iFound += 1;
					if (typeof settings.maxResults === 'number' && iFound >= settings.maxResults) {
						break;
					}
				}
				
				if ($('li', results).length > 0) {
					currentSelection = undefined;
					$(results).show().css('height', 'auto');
					
					if ($(results).height() > settings.maxHeight) {
						$(results).css({'overflow': 'auto', 'height': settings.maxHeight + 'px'});
					}
				}
			}
			
			/**
			* Prepare the search data based on the settings for this plugin,
			* run a match against each item in the possible results and display any 
			* results on the page allowing selection by the user.
			*/
			function runSuggest(e) {	
				var search = function(searchData) {
					var resultObjects = [],
						filterTxt = (!settings.wildCard) ? regexEscape(this.value) : regexEscape(this.value, settings.wildCard).replace(wildCardPatt, '.*'),
						bMatch = true, 
						filterPatt, i;
							
					if (settings.notCharacter && filterTxt.indexOf(settings.notCharacter) === 0) {
						filterTxt = filterTxt.substr(settings.notCharacter.length,filterTxt.length);
						if (filterTxt.length > 0) { bMatch = false; }
					}
					filterTxt = filterTxt || '.*';
					filterTxt = settings.wildCard ? '^' + filterTxt : filterTxt;
					filterPatt = settings.caseSensitive ? new RegExp(filterTxt) : new RegExp(filterTxt, 'i');
					
					// Look for the required match against each single search data item. When the not
					// character is used we are looking for a false match. 
					for (i = 0; i < searchData.length; i += 1) {
						if (filterPatt.test(searchData[i].text) === bMatch) {
							resultObjects.push(searchData[i]);
						}
					}
					
					buildResults(resultObjects, filterTxt);
				};
				
				if (this.value.length < settings.minCharacters) {
					$(results).html('').hide();
					return false;
				}
				
				if (settings.data && settings.data.length) {
					search.apply(this, [settings.data]);
				}
				else if (settings.url && typeof settings.url === 'string') {
					var text = this.value;
					$(results).html('<li class="ui-menu-item ajaxSearching"><a class="ui-corner-all">Searching...</a></li>').
						show().css('height', 'auto');
					
					getJSONTimeout = window.clearTimeout(getJSONTimeout);
					getJSONTimeout = window.setTimeout(function() {
						$.getJSON(settings.url, {search: text}, function(data) {
							if (data) {
								buildResults(data, text);
							}
							else {
								$(results).html('').hide();
							}
						});
					}, 500);
				}
			}
			
			/**
			* To call specific actions based on the keys pressed in the input
			* box. Special keys are up, down and return. All other keys
			* act as normal.
			*/
			function keyListener(e) {
				switch (e.keyCode) {
					case 13: // return key
						$(currentSelection).trigger('click');
						return false;
					case 40: // down key
						if (typeof currentSelection === 'undefined') {
							currentSelection = $('li:first', results).get(0);
						}
						else {
							currentSelection = $(currentSelection).next().get(0);
						}
						
						setHoverClass(currentSelection);
						if (currentSelection) {
							$(results).scrollTop(currentSelection.offsetTop);
						}
						
						return false;
					case 38: // up key
						if (typeof currentSelection === 'undefined') {
							currentSelection = $('li:last', results).get(0);
						}
						else {
							currentSelection = $(currentSelection).prev().get(0);
						}
						
						setHoverClass(currentSelection);
						if (currentSelection) {
							$(results).scrollTop(currentSelection.offsetTop);
						}
						
						return false;
					default:
						runSuggest.apply(this, [e]);
				}
			}
			
			// Prepare the input box to show suggest results by adding in the events
			// that will initiate the search and placing the element on the page
			// that will show the results.
			$(results).addClass('jsonSuggest ui-autocomplete ui-menu ui-widget ui-widget-content ui-corner-all').
				attr('role', 'listbox').
				css({
					'top': (obj.position().top + obj.outerHeight()) + 'px',
					'left': obj.position().left + 'px',
					'width': settings.width || (obj.outerWidth() + 'px'),
					'z-index': 999
				}).hide();
				
			obj.after(results).
				keyup(keyListener).
				keydown(function(e) {
					// for tab key only
					if (e.keyCode === 9 && currentSelection) {
						$(currentSelection).trigger('click');
						return true;
					}
				}).
				blur(function(e) {
					// We need to make sure we don't hide the result set
					// if the input blur event is called because of clicking on
					// a result item.
					var resPos = $(results).offset();
					resPos.bottom = resPos.top + $(results).height();
					resPos.right = resPos.left + $(results).width();
					
					if (pageY < resPos.top || pageY > resPos.bottom || pageX < resPos.left || pageX > resPos.right) {
						$(results).hide();
					}
				}).
				focus(function(e) {
					$(results).css({
						'top': (obj.position().top + obj.outerHeight()) + 'px',
						'left': obj.position().left + 'px'
					});
				
					if ($('li', results).length > 0) {
						$(results).show();
					}
				}).
				attr('autocomplete', 'off');
			$(window).mousemove(function(e) {
				pageX = e.pageX;
				pageY = e.pageY;
			});
			
			// Escape the not character if present so that it doesn't act in the regular expression
			settings.notCharacter = regexEscape(settings.notCharacter || '');
			
			// Make sure the JSON data is a JavaScript object if given as a string.
			if (settings.data && typeof settings.data === 'string') {
				settings.data = $.parseJSON(settings.data);
			}
		});
	};

})(jQuery);

console.log('=============== >  jquery.jsonSuggest-2.js ');

/*
 * Jeditable - jQuery in place edit plugin
 *
 * Copyright (c) 2006-2009 Mika Tuupola, Dylan Verheul
 *
 * Licensed under the MIT license:
 *   http://www.opensource.org/licenses/mit-license.php
 *
 * Project home:
 *   http://www.appelsiini.net/projects/jeditable
 *
 * Based on editable by Dylan Verheul <dylan_at_dyve.net>:
 *    http://www.dyve.net/jquery/?editable
 *
 */

/**
  * Version 1.7.1
  *
  * ** means there is basic unit tests for this parameter. 
  *
  * @name  Jeditable
  * @type  jQuery
  * @param String  target             (POST) URL or function to send edited content to **
  * @param Hash    options            additional options 
  * @param String  options[method]    method to use to send edited content (POST or PUT) **
  * @param Function options[callback] Function to run after submitting edited content **
  * @param String  options[name]      POST parameter name of edited content
  * @param String  options[id]        POST parameter name of edited div id
  * @param Hash    options[submitdata] Extra parameters to send when submitting edited content.
  * @param String  options[type]      text, textarea or select (or any 3rd party input type) **
  * @param Integer options[rows]      number of rows if using textarea ** 
  * @param Integer options[cols]      number of columns if using textarea **
  * @param Mixed   options[height]    'auto', 'none' or height in pixels **
  * @param Mixed   options[width]     'auto', 'none' or width in pixels **
  * @param String  options[loadurl]   URL to fetch input content before editing **
  * @param String  options[loadtype]  Request type for load url. Should be GET or POST.
  * @param String  options[loadtext]  Text to display while loading external content.
  * @param Mixed   options[loaddata]  Extra parameters to pass when fetching content before editing.
  * @param Mixed   options[data]      Or content given as paramameter. String or function.**
  * @param String  options[indicator] indicator html to show when saving
  * @param String  options[tooltip]   optional tooltip text via title attribute **
  * @param String  options[event]     jQuery event such as 'click' of 'dblclick' **
  * @param String  options[submit]    submit button value, empty means no button **
  * @param String  options[cancel]    cancel button value, empty means no button **
  * @param String  options[cssclass]  CSS class to apply to input form. 'inherit' to copy from parent. **
  * @param String  options[style]     Style to apply to input form 'inherit' to copy from parent. **
  * @param String  options[select]    true or false, when true text is highlighted ??
  * @param String  options[placeholder] Placeholder text or html to insert when element is empty. **
  * @param String  options[onblur]    'cancel', 'submit', 'ignore' or function ??
  *             
  * @param Function options[onsubmit] function(settings, original) { ... } called before submit
  * @param Function options[onreset]  function(settings, original) { ... } called before reset
  * @param Function options[onerror]  function(settings, original, xhr) { ... } called on error
  *             
  * @param Hash    options[ajaxoptions]  jQuery Ajax options. See docs.jquery.com.
  *             
  */

(function($) {

    $.fn.editable = function(target, options) {
            
        if ('disable' == target) {
            $(this).data('disabled.editable', true);
            return;
        }
        if ('enable' == target) {
            $(this).data('disabled.editable', false);
            return;
        }
        if ('destroy' == target) {
            $(this)
                .unbind($(this).data('event.editable'))
                .removeData('disabled.editable')
                .removeData('event.editable');
            return;
        }
        
        var settings = $.extend({}, $.fn.editable.defaults, {target:target}, options);
        
        /* setup some functions */
        var plugin   = $.editable.types[settings.type].plugin || function() { };
        var submit   = $.editable.types[settings.type].submit || function() { };
        var buttons  = $.editable.types[settings.type].buttons 
                    || $.editable.types['defaults'].buttons;
        var content  = $.editable.types[settings.type].content 
                    || $.editable.types['defaults'].content;
        var element  = $.editable.types[settings.type].element 
                    || $.editable.types['defaults'].element;
        var reset    = $.editable.types[settings.type].reset 
                    || $.editable.types['defaults'].reset;
        var callback = settings.callback || function() { };
        var onedit   = settings.onedit   || function() { }; 
        var onsubmit = settings.onsubmit || function() { };
        var onreset  = settings.onreset  || function() { };
        var onerror  = settings.onerror  || reset;
          
        /* show tooltip */
        if (settings.tooltip) {
            $(this).attr('title', settings.tooltip);
        }
        
        settings.autowidth  = 'auto' == settings.width;
        settings.autoheight = 'auto' == settings.height;
        
        return this.each(function() {
                        
            /* save this to self because this changes when scope changes */
            var self = this;  
                   
            /* inlined block elements lose their width and height after first edit */
            /* save them for later use as workaround */
            var savedwidth  = $(self).width();
            var savedheight = $(self).height();
            
            /* save so it can be later used by $.editable('destroy') */
            $(this).data('event.editable', settings.event);
            
            /* if element is empty add something clickable (if requested) */
            if (!$.trim($(this).html())) {
                $(this).html(settings.placeholder);
            }
            
            $(this).bind(settings.event, function(e) {
                
                /* abort if disabled for this element */
                if (true === $(this).data('disabled.editable')) {
                    return;
                }
                
                /* prevent throwing an exeption if edit field is clicked again */
                if (self.editing) {
                    return;
                }
                
                /* abort if onedit hook returns false */
                if (false === onedit.apply(this, [settings, self])) {
                   return;
                }
                
                /* prevent default action and bubbling */
                e.preventDefault();
                e.stopPropagation();
                
                /* remove tooltip */
                if (settings.tooltip) {
                    $(self).removeAttr('title');
                }
                
                /* figure out how wide and tall we are, saved width and height */
                /* are workaround for http://dev.jquery.com/ticket/2190 */
                if (0 == $(self).width()) {
                    //$(self).css('visibility', 'hidden');
                    settings.width  = savedwidth;
                    settings.height = savedheight;
                } else {
                    if (settings.width != 'none') {
                        settings.width = 
                            settings.autowidth ? $(self).width()  : settings.width;
                    }
                    if (settings.height != 'none') {
                        settings.height = 
                            settings.autoheight ? $(self).height() : settings.height;
                    }
                }
                //$(this).css('visibility', '');
                
                /* remove placeholder text, replace is here because of IE */
                if ($(this).html().toLowerCase().replace(/(;|")/g, '') == 
                    settings.placeholder.toLowerCase().replace(/(;|")/g, '')) {
                        $(this).html('');
                }
                                
                self.editing    = true;
                self.revert     = $(self).html();
                $(self).html('');

                /* create the form object */
                var form = $('<form />');
                
                /* apply css or style or both */
                if (settings.cssclass) {
                    if ('inherit' == settings.cssclass) {
                        form.attr('class', $(self).attr('class'));
                    } else {
                        form.attr('class', settings.cssclass);
                    }
                }

                if (settings.style) {
                    if ('inherit' == settings.style) {
                        form.attr('style', $(self).attr('style'));
                        /* IE needs the second line or display wont be inherited */
                        form.css('display', $(self).css('display'));                
                    } else {
                        form.attr('style', settings.style);
                    }
                }

                /* add main input element to form and store it in input */
                var input = element.apply(form, [settings, self]);

                /* set input content via POST, GET, given data or existing value */
                var input_content;
                
                if (settings.loadurl) {
                    var t = setTimeout(function() {
                        input.disabled = true;
                        content.apply(form, [settings.loadtext, settings, self]);
                    }, 100);

                    var loaddata = {};
                    loaddata[settings.id] = self.id;
                    if ($.isFunction(settings.loaddata)) {
                        $.extend(loaddata, settings.loaddata.apply(self, [self.revert, settings]));
                    } else {
                        $.extend(loaddata, settings.loaddata);
                    }
                    $.ajax({
                       type : settings.loadtype,
                       url  : settings.loadurl,
                       data : loaddata,
                       async : false,
                       success: function(result) {
                          window.clearTimeout(t);
                          input_content = result;
                          input.disabled = false;
                       }
                    });
                } else if (settings.data) {
                    input_content = settings.data;
                    if ($.isFunction(settings.data)) {
                        input_content = settings.data.apply(self, [self.revert, settings]);
                    }
                } else {
                    input_content = self.revert; 
                }
                content.apply(form, [input_content, settings, self]);

                input.attr('name', settings.name);
        
                /* add buttons to the form */
                buttons.apply(form, [settings, self]);
         
                /* add created form to self */
                $(self).append(form);
         
                /* attach 3rd party plugin if requested */
                plugin.apply(form, [settings, self]);

                /* focus to first visible form element */
                $(':input:visible:enabled:first', form).focus();

                /* highlight input contents when requested */
                if (settings.select) {
                    input.select();
                }
        
                /* discard changes if pressing esc */
                input.keydown(function(e) {
                    if (e.keyCode == 27) {
                        e.preventDefault();
                        //self.reset();
                        reset.apply(form, [settings, self]);
                    }
                });

                /* discard, submit or nothing with changes when clicking outside */
                /* do nothing is usable when navigating with tab */
                var t;
                if ('cancel' == settings.onblur) {
                    input.blur(function(e) {
                        /* prevent canceling if submit was clicked */
                        t = setTimeout(function() {
                            reset.apply(form, [settings, self]);
                        }, 500);
                    });
                } else if ('submit' == settings.onblur) {
                    input.blur(function(e) {
                        /* prevent double submit if submit was clicked */
                        t = setTimeout(function() {
                            form.submit();
                        }, 200);
                    });
                } else if ($.isFunction(settings.onblur)) {
                    input.blur(function(e) {
                        settings.onblur.apply(self, [input.val(), settings]);
                    });
                } else {
                    input.blur(function(e) {
                      /* TODO: maybe something here */
                    });
                }

                form.submit(function(e) {

                    if (t) { 
                        clearTimeout(t);
                    }

                    /* do no submit */
                    e.preventDefault(); 
            
                    /* call before submit hook. */
                    /* if it returns false abort submitting */                    
                    if (false !== onsubmit.apply(form, [settings, self])) { 
                        /* custom inputs call before submit hook. */
                        /* if it returns false abort submitting */
                        if (false !== submit.apply(form, [settings, self])) { 

                          /* check if given target is function */
                          if ($.isFunction(settings.target)) {
                              var str = settings.target.apply(self, [input.val(), settings]);
                              $(self).html(str);
                              self.editing = false;
                              callback.apply(self, [self.innerHTML, settings]);
                              /* TODO: this is not dry */                              
                              if (!$.trim($(self).html())) {
                                  $(self).html(settings.placeholder);
                              }
                          } else {
                              /* add edited content and id of edited element to POST */
                              var submitdata = {};
                              submitdata[settings.name] = input.val();
                              submitdata[settings.id] = self.id;
                              /* add extra data to be POST:ed */
                              if ($.isFunction(settings.submitdata)) {
                                  $.extend(submitdata, settings.submitdata.apply(self, [self.revert, settings]));
                              } else {
                                  $.extend(submitdata, settings.submitdata);
                              }

                              /* quick and dirty PUT support */
                              if ('PUT' == settings.method) {
                                  submitdata['_method'] = 'put';
                              }

                              /* show the saving indicator */
                              $(self).html(settings.indicator);
                              
                              /* defaults for ajaxoptions */
                              var ajaxoptions = {
                                  type    : 'POST',
                                  data    : submitdata,
                                  dataType: 'html',
                                  url     : settings.target,
                                  success : function(result, status) {
                                      if (ajaxoptions.dataType == 'html') {
                                        $(self).html(result);
                                      }
                                      self.editing = false;
                                      callback.apply(self, [result, settings]);
                                      if (!$.trim($(self).html())) {
                                          $(self).html(settings.placeholder);
                                      }
                                  },
                                  error   : function(xhr, status, error) {
                                      onerror.apply(form, [settings, self, xhr]);
                                  }
                              };
                              
                              /* override with what is given in settings.ajaxoptions */
                              $.extend(ajaxoptions, settings.ajaxoptions);   
                              $.ajax(ajaxoptions);          
                              
                            }
                        }
                    }
                    
                    /* show tooltip again */
                    $(self).attr('title', settings.tooltip);
                    
                    return false;
                });
            });
            
            /* privileged methods */
            this.reset = function(form) {
                /* prevent calling reset twice when blurring */
                if (this.editing) {
                    /* before reset hook, if it returns false abort reseting */
                    if (false !== onreset.apply(form, [settings, self])) { 
                        $(self).html(self.revert);
                        self.editing   = false;
                        if (!$.trim($(self).html())) {
                            $(self).html(settings.placeholder);
                        }
                        /* show tooltip again */
                        if (settings.tooltip) {
                            $(self).attr('title', settings.tooltip);                
                        }
                    }                    
                }
            };            
        });

    };


    $.editable = {
        types: {
            defaults: {
                element : function(settings, original) {
                    var input = $('<input type="hidden"></input>');                
                    $(this).append(input);
                    return(input);
                },
                content : function(string, settings, original) {
                    $(':input:first', this).val(string);
                },
                reset : function(settings, original) {
                  original.reset(this);
                },
                buttons : function(settings, original) {
                    var form = this;
                    if (settings.submit) {
                        /* if given html string use that */
                        if (settings.submit.match(/>$/)) {
                            var submit = $(settings.submit).click(function() {
                                if (submit.attr("type") != "submit") {
                                    form.submit();
                                }
                            });
                        /* otherwise use button with given string as text */
                        } else {
                            var submit = $('<button type="submit" />');
                            submit.html(settings.submit);                            
                        }
                        $(this).append(submit);
                    }
                    if (settings.cancel) {
                        /* if given html string use that */
                        if (settings.cancel.match(/>$/)) {
                            var cancel = $(settings.cancel);
                        /* otherwise use button with given string as text */
                        } else {
                            var cancel = $('<button type="cancel" />');
                            cancel.html(settings.cancel);
                        }
                        $(this).append(cancel);

                        $(cancel).click(function(event) {
                            //original.reset();
                            if ($.isFunction($.editable.types[settings.type].reset)) {
                                var reset = $.editable.types[settings.type].reset;                                                                
                            } else {
                                var reset = $.editable.types['defaults'].reset;                                
                            }
                            reset.apply(form, [settings, original]);
                            return false;
                        });
                    }
                }
            },
            text: {
                element : function(settings, original) {
                    var input = $('<input />');
                    if (settings.width  != 'none') { input.width(settings.width);  }
                    if (settings.height != 'none') { input.height(settings.height); }
                    /* https://bugzilla.mozilla.org/show_bug.cgi?id=236791 */
                    //input[0].setAttribute('autocomplete','off');
                    input.attr('autocomplete','off');
                    $(this).append(input);
                    return(input);
                }
            },
            textarea: {
                element : function(settings, original) {
                    var textarea = $('<textarea />');
                    if (settings.rows) {
                        textarea.attr('rows', settings.rows);
                    } else if (settings.height != "none") {
                        textarea.height(settings.height);
                    }
                    if (settings.cols) {
                        textarea.attr('cols', settings.cols);
                    } else if (settings.width != "none") {
                        textarea.width(settings.width);
                    }
                    $(this).append(textarea);
                    return(textarea);
                }
            },
            select: {
               element : function(settings, original) {
                    var select = $('<select />');
                    $(this).append(select);
                    return(select);
                },
                content : function(data, settings, original) {
                    /* If it is string assume it is json. */
                    if (String == data.constructor) {      
                        eval ('var json = ' + data);
                    } else {
                    /* Otherwise assume it is a hash already. */
                        var json = data;
                    }
                    for (var key in json) {
                        if (!json.hasOwnProperty(key)) {
                            continue;
                        }
                        if ('selected' == key) {
                            continue;
                        } 
                        var option = $('<option />').val(key).append(json[key]);
                        $('select', this).append(option);    
                    }                    
                    /* Loop option again to set selected. IE needed this... */ 
                    $('select', this).children().each(function() {
                        if ($(this).val() == json['selected'] || 
                            $(this).text() == $.trim(original.revert)) {
                                $(this).attr('selected', 'selected');
                        }
                    });
                }
            }
        },

        /* Add new input type */
        addInputType: function(name, input) {
            $.editable.types[name] = input;
        }
    };

    // publicly accessible defaults
    $.fn.editable.defaults = {
        name       : 'value',
        id         : 'id',
        type       : 'text',
        width      : 'auto',
        height     : 'auto',
        event      : 'click.editable',
        onblur     : 'cancel',
        loadtype   : 'GET',
        loadtext   : 'Loading...',
        placeholder: 'Click to edit',
        loaddata   : {},
        submitdata : {},
        ajaxoptions: {}
    };

})(jQuery);
console.log('=============== >  jquery.jeditable.js ');

/**
 * @preserve jquery.layout 1.3.0 - Release Candidate 30.4
 * $Date: 2012-03-10 08:00:00 (Sat, 10 Mar 2012) $
 * $Rev: 303004 $
 *
 * Copyright (c) 2012 
 *   Fabrizio Balliano (http://www.fabrizioballiano.net)
 *   Kevin Dalman (http://allpro.net)
 *
 * Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html)
 * and MIT (http://www.opensource.org/licenses/mit-license.php) licenses.
 *
 * Changelog: http://layout.jquery-dev.net/changelog.cfm#1.3.0.rc30.2
 *
 * Docs: http://layout.jquery-dev.net/documentation.html
 * Tips: http://layout.jquery-dev.net/tips.html
 * Help: http://groups.google.com/group/jquery-ui-layout
 */

/* JavaDoc Info: http://code.google.com/closure/compiler/docs/js-for-compiler.html
 * {!Object}	non-nullable type (never NULL)
 * {?string}	nullable type (sometimes NULL) - default for {Object}
 * {number=}	optional parameter
 * {*}			ALL types
 */

// NOTE: For best readability, view with a fixed-width font and tabs equal to 4-chars

;(function ($) {

// alias Math methods - used a lot!
var	min		= Math.min
,	max		= Math.max
,	round	= Math.floor
;
function isStr (v) { return $.type(v) === "string"; }

function runPluginCallbacks (Instance, a_fn) {
	if ($.isArray(a_fn)) 
		for (var i=0, c=a_fn.length; i<c; i++) {
			var fn = a_fn[i];
			try {
				if (isStr(fn)) // 'name' of a function
					fn = eval(fn);
				if ($.isFunction(fn))
					fn( Instance );
			} catch (ex) {}
		}
};


/*
 *	GENERIC $.layout METHODS - used by all layouts
 */
$.layout = {

	version:	"1.3.rc30.4"
,	revision:	0.033004 // 1.3.0 final = 1.0300 - major(n+).minor(nn)+patch(nn+)

	// LANGUAGE CUSTOMIZATION
,	language: {
	//	Tips and messages for resizers, togglers, custom buttons, etc.
		Open:			"Open"	// eg: "Open Pane"
	,	Close:			"Close"
	,	Resize:			"Resize"
	,	Slide:			"Slide Open"
	,	Pin:			"Pin"
	,	Unpin:			"Un-Pin"
	,	noRoomToOpenTip: "Not enough room to show this pane."
	,	minSizeWarning:	"Panel has reached its minimum size"
	,	maxSizeWarning:	"Panel has reached its maximum size"
	//	Developer error messages
	,	pane:					"pane"		// description of "layout pane element"
	,	selector:				"selector"	// description of "jQuery-selector"
	,	errButton:				"Error Adding Button \n\nInvalid "
	,	errContainerMissing:	"UI Layout Initialization Error\n\nThe specified layout-container does not exist."
	,	errCenterPaneMissing:	"UI Layout Initialization Error\n\nThe center-pane element does not exist.\n\nThe center-pane is a required element."
	,	errContainerHeight:		"UI Layout Initialization Warning\n\nThe layout-container \"CONTAINER\" has no height.\n\nTherefore the layout is 0-height and hence 'invisible'!"
	}

	// can update code here if $.browser is phased out
,	browser: {
		mozilla:	!!$.browser.mozilla
	,	webkit:		!!$.browser.webkit || !!$.browser.safari // webkit = jQ 1.4
	,	msie:		!!$.browser.msie
	,	isIE6:		!!$.browser.msie && $.browser.version == 6
	,	version:	$.browser.version // not used in Layout core, but may be used by plugins
	}

	// *PREDEFINED* EFFECTS & DEFAULTS 
	// MUST list effect here - OR MUST set an fxSettings option (can be an empty hash: {})
,	effects: {

	//	Pane Open/Close Animations
		slide: {
			all:	{ duration:  "fast"	} // eg: duration: 1000, easing: "easeOutBounce"
		,	north:	{ direction: "up"	}
		,	south:	{ direction: "down"	}
		,	east:	{ direction: "right"}
		,	west:	{ direction: "left"	}
		}
	,	drop: {
			all:	{ duration:  "slow"	} // eg: duration: 1000, easing: "easeOutQuint"
		,	north:	{ direction: "up"	}
		,	south:	{ direction: "down"	}
		,	east:	{ direction: "right"}
		,	west:	{ direction: "left"	}
		}
	,	scale: {
			all:	{ duration:	"fast"	}
		}
	//	these are not recommended, but can be used
	,	blind:		{}
	,	clip:		{}
	,	explode:	{}
	,	fade:		{}
	,	fold:		{}
	,	puff:		{}

	//	Pane Resize Animations
	,	size: {
			all:	{ easing:	"swing"	}
		}
	}

	// INTERNAL CONFIG DATA - DO NOT CHANGE THIS!
,	config: {
		optionRootKeys:	"effects,panes,north,south,west,east,center".split(",")
	,	allPanes:		"north,south,west,east,center".split(",")
	,	borderPanes:	"north,south,west,east".split(",")
	,	oppositeEdge: {
			north:	"south"
		,	south:	"north"
		,	east: 	"west"
		,	west: 	"east"
		}
	//	CSS used in multiple places
	,	hidden:  { visibility: "hidden" }
	,	visible: { visibility: "visible" }
	//	layout element settings
	,	resizers: {
			cssReq: {
				position: 	"absolute"
			,	padding: 	0
			,	margin: 	0
			,	fontSize:	"1px"
			,	textAlign:	"left"	// to counter-act "center" alignment!
			,	overflow: 	"hidden" // prevent toggler-button from overflowing
			//	SEE $.layout.defaults.zIndexes.resizer_normal
			}
		,	cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true
				background: "#DDD"
			,	border:		"none"
			}
		}
	,	togglers: {
			cssReq: {
				position: 	"absolute"
			,	display: 	"block"
			,	padding: 	0
			,	margin: 	0
			,	overflow:	"hidden"
			,	textAlign:	"center"
			,	fontSize:	"1px"
			,	cursor: 	"pointer"
			,	zIndex: 	1
			}
		,	cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true
				background: "#AAA"
			}
		}
	,	content: {
			cssReq: {
				position:	"relative" /* contain floated or positioned elements */
			}
		,	cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true
				overflow:	"auto"
			,	padding:	"10px"
			}
		,	cssDemoPane: { // DEMO CSS - REMOVE scrolling from 'pane' when it has a content-div
				overflow:	"hidden"
			,	padding:	0
			}
		}
	,	panes: { // defaults for ALL panes - overridden by 'per-pane settings' below
			cssReq: {
				position: 	"absolute"
			,	margin:		0
			//	$.layout.defaults.zIndexes.pane_normal
			}
		,	cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true
				padding:	"10px"
			,	background:	"#FFF"
			,	border:		"1px solid #BBB"
			,	overflow:	"auto"
			}
		}
	,	north: {
			side:			"Top"
		,	sizeType:		"Height"
		,	dir:			"horz"
		,	cssReq: {
				top: 		0
			,	bottom: 	"auto"
			,	left: 		0
			,	right: 		0
			,	width: 		"auto"
			//	height: 	DYNAMIC
			}
		}
	,	south: {
			side:			"Bottom"
		,	sizeType:		"Height"
		,	dir:			"horz"
		,	cssReq: {
				top: 		"auto"
			,	bottom: 	0
			,	left: 		0
			,	right: 		0
			,	width: 		"auto"
			//	height: 	DYNAMIC
			}
		}
	,	east: {
			side:			"Right"
		,	sizeType:		"Width"
		,	dir:			"vert"
		,	cssReq: {
				left: 		"auto"
			,	right: 		0
			,	top: 		"auto" // DYNAMIC
			,	bottom: 	"auto" // DYNAMIC
			,	height: 	"auto"
			//	width: 		DYNAMIC
			}
		}
	,	west: {
			side:			"Left"
		,	sizeType:		"Width"
		,	dir:			"vert"
		,	cssReq: {
				left: 		0
			,	right: 		"auto"
			,	top: 		"auto" // DYNAMIC
			,	bottom: 	"auto" // DYNAMIC
			,	height: 	"auto"
			//	width: 		DYNAMIC
			}
		}
	,	center: {
			dir:			"center"
		,	cssReq: {
				left: 		"auto" // DYNAMIC
			,	right: 		"auto" // DYNAMIC
			,	top: 		"auto" // DYNAMIC
			,	bottom: 	"auto" // DYNAMIC
			,	height: 	"auto"
			,	width: 		"auto"
			}
		}
	}

	// CALLBACK FUNCTION NAMESPACE - used to store reusable callback functions
,	callbacks: {}

	// LAYOUT-PLUGIN REGISTRATION
	// more plugins can added beyond this default list
,	plugins: {
		draggable:		!!$.fn.draggable // resizing
	,	effects: {
			core:		!!$.effects		// animimations (specific effects tested by initOptions)
		,	slide:		$.effects && $.effects.slide // default effect
		}
	}

//	arrays of plugin or other methods to be triggered for events in *each layout* - will be passed 'Instance'
,	onCreate:	[]	// runs when layout is just starting to be created - right after options are set
,	onLoad:		[]	// runs after layout container and global events init, but before initPanes is called
,	onReady:	[]	// runs after initialization *completes* - ie, after initPanes completes successfully
,	onDestroy:	[]	// runs after layout is destroyed
,	onUnload:	[]	// runs after layout is destroyed OR when page unloads
,	afterOpen:	[]	// runs after setAsOpen() completes
,	afterClose:	[]	// runs after setAsClosed() completes

	/*
	*	GENERIC UTILITY METHODS
	*/

	// calculate and return the scrollbar width, as an integer
,	scrollbarWidth:		function () { return window.scrollbarWidth  || $.layout.getScrollbarSize('width'); }
,	scrollbarHeight:	function () { return window.scrollbarHeight || $.layout.getScrollbarSize('height'); }
,	getScrollbarSize:	function (dim) {
		var $c	= $('<div style="position: absolute; top: -10000px; left: -10000px; width: 100px; height: 100px; overflow: scroll;"></div>').appendTo("body");
		var d	= { width: $c.width() - $c[0].clientWidth, height: $c.height() - $c[0].clientHeight };
		$c.remove();
		window.scrollbarWidth	= d.width;
		window.scrollbarHeight	= d.height;
		return dim.match(/^(width|height)$/) ? d[dim] : d;
	}


	/**
	* Returns hash container 'display' and 'visibility'
	*
	* @see	$.swap() - swaps CSS, runs callback, resets CSS
	*/
,	showInvisibly: function ($E, force) {
		if (!$E) return {};
		if (!$E.jquery) $E = $($E);
		var CSS = {
			display:	$E.css('display')
		,	visibility:	$E.css('visibility')
		};
		if (force || CSS.display === "none") { // only if not *already hidden*
			$E.css({ display: "block", visibility: "hidden" }); // show element 'invisibly' so can be measured
			return CSS;
		}
		else return {};
	}

	/**
	* Returns data for setting size of an element (container or a pane).
	*
	* @see  _create(), onWindowResize() for container, plus others for pane
	* @return JSON  Returns a hash of all dimensions: top, bottom, left, right, outerWidth, innerHeight, etc
	*/
,	getElementDimensions: function ($E) {
		var
			d	= {}			// dimensions hash
		,	x	= d.css = {}	// CSS hash
		,	i	= {}			// TEMP insets
		,	b, p				// TEMP border, padding
		,	N	= $.layout.cssNum
		,	off = $E.offset()
		;
		d.offsetLeft = off.left;
		d.offsetTop  = off.top;

		$.each("Left,Right,Top,Bottom".split(","), function (idx, e) { // e = edge
			b = x["border" + e] = $.layout.borderWidth($E, e);
			p = x["padding"+ e] = $.layout.cssNum($E, "padding"+e);
			i[e] = b + p; // total offset of content from outer side
			d["inset"+ e] = p;
		});

		d.offsetWidth	= $E.innerWidth();	// offsetWidth is used in calc when doing manual resize
		d.offsetHeight	= $E.innerHeight();	// ditto
		d.outerWidth	= $E.outerWidth();
		d.outerHeight	= $E.outerHeight();
		d.innerWidth	= max(0, d.outerWidth  - i.Left - i.Right);
		d.innerHeight	= max(0, d.outerHeight - i.Top  - i.Bottom);

		x.width		= $E.width();
		x.height	= $E.height();
		x.top		= N($E,"top",true);
		x.bottom	= N($E,"bottom",true);
		x.left		= N($E,"left",true);
		x.right		= N($E,"right",true);

		//d.visible	= $E.is(":visible");// && x.width > 0 && x.height > 0;

		return d;
	}

,	getElementCSS: function ($E, list) {
		var
			CSS	= {}
		,	style	= $E[0].style
		,	props	= list.split(",")
		,	sides	= "Top,Bottom,Left,Right".split(",")
		,	attrs	= "Color,Style,Width".split(",")
		,	p, s, a, i, j, k
		;
		for (i=0; i < props.length; i++) {
			p = props[i];
			if (p.match(/(border|padding|margin)$/))
				for (j=0; j < 4; j++) {
					s = sides[j];
					if (p === "border")
						for (k=0; k < 3; k++) {
							a = attrs[k];
							CSS[p+s+a] = style[p+s+a];
						}
					else
						CSS[p+s] = style[p+s];
				}
			else
				CSS[p] = style[p];
		};
		return CSS
	}

	/**
	* Return the innerWidth for the current browser/doctype
	*
	* @see  initPanes(), sizeMidPanes(), initHandles(), sizeHandles()
	* @param  {Array.<Object>}	$E  Must pass a jQuery object - first element is processed
	* @param  {number=}			outerWidth (optional) Can pass a width, allowing calculations BEFORE element is resized
	* @return {number}			Returns the innerWidth of the elem by subtracting padding and borders
	*/
,	cssWidth: function ($E, outerWidth) {
		var
			b = $.layout.borderWidth
		,	n = $.layout.cssNum
		;
		// a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed
		if (outerWidth <= 0) return 0;

		if (!$.support.boxModel) return outerWidth;

		// strip border and padding from outerWidth to get CSS Width
		var W = outerWidth
			- b($E, "Left")
			- b($E, "Right")
			- n($E, "paddingLeft")		
			- n($E, "paddingRight")
		;

		return max(0,W);
	}

	/**
	* Return the innerHeight for the current browser/doctype
	*
	* @see  initPanes(), sizeMidPanes(), initHandles(), sizeHandles()
	* @param  {Array.<Object>}	$E  Must pass a jQuery object - first element is processed
	* @param  {number=}			outerHeight  (optional) Can pass a width, allowing calculations BEFORE element is resized
	* @return {number}			Returns the innerHeight of the elem by subtracting padding and borders
	*/
,	cssHeight: function ($E, outerHeight) {
		var
			b = $.layout.borderWidth
		,	n = $.layout.cssNum
		;
		// a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed
		if (outerHeight <= 0) return 0;

		if (!$.support.boxModel) return outerHeight;

		// strip border and padding from outerHeight to get CSS Height
		var H = outerHeight
			- b($E, "Top")
			- b($E, "Bottom")
			- n($E, "paddingTop")
			- n($E, "paddingBottom")
		;

		return max(0,H);
	}

	/**
	* Returns the 'current CSS numeric value' for a CSS property - 0 if property does not exist
	*
	* @see  Called by many methods
	* @param {Array.<Object>}	$E					Must pass a jQuery object - first element is processed
	* @param {string}			prop				The name of the CSS property, eg: top, width, etc.
	* @param {boolean=}			[allowAuto=false]	true = return 'auto' if that is value; false = return 0
	* @return {(string|number)}						Usually used to get an integer value for position (top, left) or size (height, width)
	*/
,	cssNum: function ($E, prop, allowAuto) {
		if (!$E.jquery) $E = $($E);
		var CSS = $.layout.showInvisibly($E)
		,	p	= $.curCSS($E[0], prop, true)
		,	v	= allowAuto && p=="auto" ? p : (parseInt(p, 10) || 0);
		$E.css( CSS ); // RESET
		return v;
	}

,	borderWidth: function (el, side) {
		if (el.jquery) el = el[0];
		var b = "border"+ side.substr(0,1).toUpperCase() + side.substr(1); // left => Left
		return $.curCSS(el, b+"Style", true) === "none" ? 0 : (parseInt($.curCSS(el, b+"Width", true), 10) || 0);
	}

	/**
	* Mouse-tracking utility - FUTURE REFERENCE
	*
	* init: if (!window.mouse) {
	*			window.mouse = { x: 0, y: 0 };
	*			$(document).mousemove( $.layout.trackMouse );
	*		}
	*
	* @param {Object}		evt
	*
,	trackMouse: function (evt) {
		window.mouse = { x: evt.clientX, y: evt.clientY };
	}
	*/

	/**
	* SUBROUTINE for preventPrematureSlideClose option
	*
	* @param {Object}		evt
	* @param {Object=}		el
	*/
,	isMouseOverElem: function (evt, el) {
		var
			$E	= $(el || this)
		,	d	= $E.offset()
		,	T	= d.top
		,	L	= d.left
		,	R	= L + $E.outerWidth()
		,	B	= T + $E.outerHeight()
		,	x	= evt.pageX	// evt.clientX ?
		,	y	= evt.pageY	// evt.clientY ?
		;
		// if X & Y are < 0, probably means is over an open SELECT
		return ($.layout.browser.msie && x < 0 && y < 0) || ((x >= L && x <= R) && (y >= T && y <= B));
	}

	/**
	* Message/Logging Utility
	*
	* @example $.layout.msg("My message");				// log text
	* @example $.layout.msg("My message", true);		// alert text
	* @example $.layout.msg({ foo: "bar" }, "Title");	// log hash-data, with custom title
	* @example $.layout.msg({ foo: "bar" }, true, "Title", { sort: false }); -OR-
	* @example $.layout.msg({ foo: "bar" }, "Title", { sort: false, display: true }); // alert hash-data
	*
	* @param {(Object|string)}			info			String message OR Hash/Array
	* @param {(Boolean|string|Object)=}	[popup=false]	True means alert-box - can be skipped
	* @param {(Object|string)=}			[debugTitle=""]	Title for Hash data - can be skipped
	* @param {Object=}					[debutOpts={}]	Extra options for debug output
	*/
,	msg: function (info, popup, debugTitle, debugOpts) {
		if ($.isPlainObject(info) && window.debugData) {
			if (typeof popup === "string") {
				debugOpts	= debugTitle;
				debugTitle	= popup;
			}
			else if (typeof debugTitle === "object") {
				debugOpts	= debugTitle;
				debugTitle	= null;
			}
			var t = debugTitle || "log( <object> )"
			,	o = $.extend({ sort: false, returnHTML: false, display: false }, debugOpts);
			if (popup === true || o.display)
				debugData( info, t, o );
			else if (window.console)
				console.log(debugData( info, t, o ));
		}
		else if (popup)
			alert(info);
		else if (window.console)
			console.log(info);
	}

};

var	lang = $.layout.language; // alias used in defaults...

// DEFAULT OPTIONS - CHANGE IF DESIRED
$.layout.defaults = {
/*
 *	LAYOUT & LAYOUT-CONTAINER OPTIONS
 *	- none of these options are applicable to individual panes
 */
	name:						""			// Not required, but useful for buttons and used for the state-cookie
,	containerClass:				"ui-layout-container" // layout-container element
,	scrollToBookmarkOnLoad:		true		// after creating a layout, scroll to bookmark in URL (.../page.htm#myBookmark)
,	resizeWithWindow:			true		// bind thisLayout.resizeAll() to the window.resize event
,	resizeWithWindowDelay:		200			// delay calling resizeAll because makes window resizing very jerky
,	resizeWithWindowMaxDelay:	0			// 0 = none - force resize every XX ms while window is being resized
,	onresizeall_start:			null		// CALLBACK when resizeAll() STARTS	- NOT pane-specific
,	onresizeall_end:			null		// CALLBACK when resizeAll() ENDS	- NOT pane-specific
,	onload_start:				null		// CALLBACK when Layout inits - after options initialized, but before elements
,	onload_end:					null		// CALLBACK when Layout inits - after EVERYTHING has been initialized
,	onunload_start:				null		// CALLBACK when Layout is destroyed OR onWindowUnload
,	onunload_end:				null		// CALLBACK when Layout is destroyed OR onWindowUnload
,	autoBindCustomButtons:		false		// search for buttons with ui-layout-button class and auto-bind them
,	initPanes:					true		// false = DO NOT initialize the panes onLoad - will init later
,	showErrorMessages:			true		// enables fatal error messages to warn developers of common errors
,	showDebugMessages:			false		// display console-and-alert debug msgs - IF this Layout version _has_ debugging code!
//	Changing this zIndex value will cause other zIndex values to automatically change
,	zIndex:						null		// the PANE zIndex - resizers and masks will be +1
//	DO NOT CHANGE the zIndex values below unless you clearly understand their relationships
,	zIndexes: {								// set _default_ z-index values here...
		pane_normal:			0			// normal z-index for panes
	,	content_mask:			1			// applied to overlays used to mask content INSIDE panes during resizing
	,	resizer_normal:			2			// normal z-index for resizer-bars
	,	pane_sliding:			100			// applied to *BOTH* the pane and its resizer when a pane is 'slid open'
	,	pane_animate:			1000		// applied to the pane when being animated - not applied to the resizer
	,	resizer_drag:			10000		// applied to the CLONED resizer-bar when being 'dragged'
	}
/*
 *	PANE DEFAULT SETTINGS
 *	- settings under the 'panes' key become the default settings for *all panes*
 *	- ALL pane-options can also be set specifically for each panes, which will override these 'default values'
 */
,	panes: { // default options for 'all panes' - will be overridden by 'per-pane settings'
		applyDemoStyles: 		false		// NOTE: renamed from applyDefaultStyles for clarity
	,	closable:				true		// pane can open & close
	,	resizable:				true		// when open, pane can be resized 
	,	slidable:				true		// when closed, pane can 'slide open' over other panes - closes on mouse-out
	,	initClosed:				false		// true = init pane as 'closed'
	,	initHidden: 			false 		// true = init pane as 'hidden' - no resizer-bar/spacing
	//	SELECTORS
	//,	paneSelector:			""			// MUST be pane-specific - jQuery selector for pane
	,	contentSelector:		".ui-layout-content" // INNER div/element to auto-size so only it scrolls, not the entire pane!
	,	contentIgnoreSelector:	".ui-layout-ignore"	// element(s) to 'ignore' when measuring 'content'
	,	findNestedContent:		false		// true = $P.find(contentSelector), false = $P.children(contentSelector)
	//	GENERIC ROOT-CLASSES - for auto-generated classNames
	,	paneClass:				"ui-layout-pane"	// Layout Pane
	,	resizerClass:			"ui-layout-resizer"	// Resizer Bar
	,	togglerClass:			"ui-layout-toggler"	// Toggler Button
	,	buttonClass:			"ui-layout-button"	// CUSTOM Buttons	- eg: '[ui-layout-button]-toggle/-open/-close/-pin'
	//	ELEMENT SIZE & SPACING
	//,	size:					100			// MUST be pane-specific -initial size of pane
	,	minSize:				0			// when manually resizing a pane
	,	maxSize:				0			// ditto, 0 = no limit
	,	spacing_open:			6			// space between pane and adjacent panes - when pane is 'open'
	,	spacing_closed:			6			// ditto - when pane is 'closed'
	,	togglerLength_open:		50			// Length = WIDTH of toggler button on north/south sides - HEIGHT on east/west sides
	,	togglerLength_closed: 	50			// 100% OR -1 means 'full height/width of resizer bar' - 0 means 'hidden'
	,	togglerAlign_open:		"center"	// top/left, bottom/right, center, OR...
	,	togglerAlign_closed:	"center"	// 1 => nn = offset from top/left, -1 => -nn == offset from bottom/right
	,	togglerTip_open:		lang.Close	// Toggler tool-tip (title)
	,	togglerTip_closed:		lang.Open	// ditto
	,	togglerContent_open:	""			// text or HTML to put INSIDE the toggler
	,	togglerContent_closed:	""			// ditto
	//	RESIZING OPTIONS
	,	resizerDblClickToggle:	true		// 
	,	autoResize:				true		// IF size is 'auto' or a percentage, then recalc 'pixel size' whenever the layout resizes
	,	autoReopen:				true		// IF a pane was auto-closed due to noRoom, reopen it when there is room? False = leave it closed
	,	resizerDragOpacity:		1			// option for ui.draggable
	//,	resizerCursor:			""			// MUST be pane-specific - cursor when over resizer-bar
	,	maskContents:			false		// true = add DIV-mask over-or-inside this pane so can 'drag' over IFRAMES
	,	maskObjects:			false		// true = add IFRAME-mask over-or-inside this pane to cover objects/applets - content-mask will overlay this mask
	,	maskZindex:				null		// will override zIndexes.content_mask if specified - not applicable to iframe-panes
	,	resizingGrid:			false		// grid size that the resizers will snap-to during resizing, eg: [20,20]
	,	livePaneResizing:		false		// true = LIVE Resizing as resizer is dragged
	,	liveContentResizing:	false		// true = re-measure header/footer heights as resizer is dragged
	,	liveResizingTolerance:	1			// how many px change before pane resizes, to control performance
	//	TIPS & MESSAGES - also see lang object
	,	noRoomToOpenTip:		lang.noRoomToOpenTip
	,	resizerTip:				lang.Resize	// Resizer tool-tip (title)
	,	sliderTip:				lang.Slide	// resizer-bar triggers 'sliding' when pane is closed
	,	sliderCursor:			"pointer"	// cursor when resizer-bar will trigger 'sliding'
	,	slideTrigger_open:		"click"		// click, dblclick, mouseenter
	,	slideTrigger_close:		"mouseleave"// click, mouseleave
	,	slideDelay_open:		300			// applies only for mouseenter event - 0 = instant open
	,	slideDelay_close:		300			// applies only for mouseleave event (300ms is the minimum!)
	,	hideTogglerOnSlide:		false		// when pane is slid-open, should the toggler show?
	,	preventQuickSlideClose:	$.layout.browser.webkit // Chrome triggers slideClosed as it is opening
	,	preventPrematureSlideClose: false	// handle incorrect mouseleave trigger, like when over a SELECT-list in IE
	//	HOT-KEYS & MISC
	,	showOverflowOnHover:	false		// will bind allowOverflow() utility to pane.onMouseOver
	,	enableCursorHotkey:		true		// enabled 'cursor' hotkeys
	//,	customHotkey:			""			// MUST be pane-specific - EITHER a charCode OR a character
	,	customHotkeyModifier:	"SHIFT"		// either 'SHIFT', 'CTRL' or 'CTRL+SHIFT' - NOT 'ALT'
	//	PANE ANIMATION
	//	NOTE: fxSss_open, fxSss_close & fxSss_size options (eg: fxName_open) are auto-generated if not passed
	,	fxName:					"slide" 	// ('none' or blank), slide, drop, scale -- only relevant to 'open' & 'close', NOT 'size'
	,	fxSpeed:				null		// slow, normal, fast, 200, nnn - if passed, will OVERRIDE fxSettings.duration
	,	fxSettings:				{}			// can be passed, eg: { easing: "easeOutBounce", duration: 1500 }
	,	fxOpacityFix:			true		// tries to fix opacity in IE to restore anti-aliasing after animation
	,	animatePaneSizing:		false		// true = animate resizing after dragging resizer-bar OR sizePane() is called
    /*  NOTE: Action-specific FX options are auto-generated from the options above if not specifically set:
		fxName_open:			"slide"		// 'Open' pane animation
		fnName_close:			"slide"		// 'Close' pane animation
		fxName_size:			"slide"		// 'Size' pane animation - when animatePaneSizing = true
		fxSpeed_open:			null
		fxSpeed_close:			null
		fxSpeed_size:			null
		fxSettings_open:		{}
		fxSettings_close:		{}
		fxSettings_size:		{}
	*/
	//	CHILD/NESTED LAYOUTS
	,	childOptions:			null		// Layout-options for nested/child layout - even {} is valid as options
	,	initChildLayout:		true		// true = child layout will be created as soon as _this_ layout completes initialization
	,	destroyChildLayout:		true		// true = destroy child-layout if this pane is destroyed
	,	resizeChildLayout:		true		// true = trigger child-layout.resizeAll() when this pane is resized
	//	PANE CALLBACKS
	,	triggerEventsOnLoad:	false		// true = trigger onopen OR onclose callbacks when layout initializes
	,	triggerEventsDuringLiveResize: true	// true = trigger onresize callback REPEATEDLY if livePaneResizing==true
	,	onshow_start:			null		// CALLBACK when pane STARTS to Show	- BEFORE onopen/onhide_start
	,	onshow_end:				null		// CALLBACK when pane ENDS being Shown	- AFTER  onopen/onhide_end
	,	onhide_start:			null		// CALLBACK when pane STARTS to Close	- BEFORE onclose_start
	,	onhide_end:				null		// CALLBACK when pane ENDS being Closed	- AFTER  onclose_end
	,	onopen_start:			null		// CALLBACK when pane STARTS to Open
	,	onopen_end:				null		// CALLBACK when pane ENDS being Opened
	,	onclose_start:			null		// CALLBACK when pane STARTS to Close
	,	onclose_end:			null		// CALLBACK when pane ENDS being Closed
	,	onresize_start:			null		// CALLBACK when pane STARTS being Resized ***FOR ANY REASON***
	,	onresize_end:			null		// CALLBACK when pane ENDS being Resized ***FOR ANY REASON***
	,	onsizecontent_start:	null		// CALLBACK when sizing of content-element STARTS
	,	onsizecontent_end:		null		// CALLBACK when sizing of content-element ENDS
	,	onswap_start:			null		// CALLBACK when pane STARTS to Swap
	,	onswap_end:				null		// CALLBACK when pane ENDS being Swapped
	,	ondrag_start:			null		// CALLBACK when pane STARTS being ***MANUALLY*** Resized
	,	ondrag_end:				null		// CALLBACK when pane ENDS being ***MANUALLY*** Resized
	}
/*
 *	PANE-SPECIFIC SETTINGS
 *	- options listed below MUST be specified per-pane - they CANNOT be set under 'panes'
 *	- all options under the 'panes' key can also be set specifically for any pane
 *	- most options under the 'panes' key apply only to 'border-panes' - NOT the the center-pane
 */
,	north: {
		paneSelector:			".ui-layout-north"
	,	size:					"auto"		// eg: "auto", "30%", .30, 200
	,	resizerCursor:			"n-resize"	// custom = url(myCursor.cur)
	,	customHotkey:			""			// EITHER a charCode (43) OR a character ("o")
	}
,	south: {
		paneSelector:			".ui-layout-south"
	,	size:					"auto"
	,	resizerCursor:			"s-resize"
	,	customHotkey:			""
	}
,	east: {
		paneSelector:			".ui-layout-east"
	,	size:					200
	,	resizerCursor:			"e-resize"
	,	customHotkey:			""
	}
,	west: {
		paneSelector:			".ui-layout-west"
	,	size:					200
	,	resizerCursor:			"w-resize"
	,	customHotkey:			""
	}
,	center: {
		paneSelector:			".ui-layout-center"
	,	minWidth:				0
	,	minHeight:				0
	}
};

$.layout.optionsMap = {
	// layout/global options - NOT pane-options
	layout: ("stateManagement,effects,zIndexes,"
	+	"name,zIndex,scrollToBookmarkOnLoad,showErrorMessages,"
	+	"resizeWithWindow,resizeWithWindowDelay,resizeWithWindowMaxDelay,"
	+	"onresizeall,onresizeall_start,onresizeall_end,onload,onunload,autoBindCustomButtons").split(",")
//	borderPanes: [ ALL options that are NOT specified as 'layout' ]
	// default.panes options that apply to the center-pane (most options apply _only_ to border-panes)
,	center: ("paneClass,contentSelector,contentIgnoreSelector,findNestedContent,applyDemoStyles,triggerEventsOnLoad,"
	+	"showOverflowOnHover,maskContents,maskObjects,liveContentResizing,"
	+	"childOptions,initChildLayout,resizeChildLayout,destroyChildLayout,"
	+	"onresize,onresize_start,onresize_end,onsizecontent,onsizecontent_start,onsizecontent_end").split(",")
	// options that MUST be specifically set 'per-pane' - CANNOT set in the panes (defaults) key
,	noDefault: ("paneSelector,resizerCursor,customHotkey").split(",")
};

/**
 * Processes options passed in converts flat-format data into subkey (JSON) format
 * In flat-format, subkeys are _currently_ separated with 2 underscores, like north__optName
 * Plugins may also call this method so they can transform their own data
 *
 * @param  {!Object}	hash	Data/options passed by user - may be a single level or nested levels
 * @return {Object}				Returns hash of minWidth & minHeight
 */
$.layout.transformData = function (hash) {
	var	json = { panes: {}, center: {} } // init return object
	,	data, branch, optKey, keys, key, val, i, c;

	if (typeof hash !== "object") return json; // no options passed

	// convert all 'flat-keys' to 'sub-key' format
	for (optKey in hash) {
		branch	= json;
		data	= $.layout.optionsMap.layout;
		val		= hash[ optKey ];
		keys	= optKey.split("__"); // eg: west__size or north__fxSettings__duration
		c		= keys.length - 1;
		// convert underscore-delimited to subkeys
		for (i=0; i <= c; i++) {
			key = keys[i];
			if (i === c)
				branch[key] = val;
			else if (!branch[key])
				branch[key] = {}; // create the subkey
			// recurse to sub-key for next loop - if not done
			branch = branch[key];
		}
	}

	return json;
}

// INTERNAL CONFIG DATA - DO NOT CHANGE THIS!
$.layout.backwardCompatibility = {
	// data used by renameOldOptions()
	map: {
	//	OLD Option Name:			NEW Option Name
		applyDefaultStyles:			"applyDemoStyles"
	,	resizeNestedLayout:			"resizeChildLayout"
	,	resizeWhileDragging:		"livePaneResizing"
	,	resizeContentWhileDragging:	"liveContentResizing"
	,	triggerEventsWhileDragging:	"triggerEventsDuringLiveResize"
	,	maskIframesOnResize:		"maskContents"
	,	useStateCookie:				"stateManagement.enabled"
	,	"cookie.autoLoad":			"stateManagement.autoLoad"
	,	"cookie.autoSave":			"stateManagement.autoSave"
	,	"cookie.keys":				"stateManagement.stateKeys"
	,	"cookie.name":				"stateManagement.cookie.name"
	,	"cookie.domain":			"stateManagement.cookie.domain"
	,	"cookie.path":				"stateManagement.cookie.path"
	,	"cookie.expires":			"stateManagement.cookie.expires"
	,	"cookie.secure":			"stateManagement.cookie.secure"
	}
	/**
	* @param {Object}	opts
	*/
,	renameOptions: function (opts) {
		var map = $.layout.backwardCompatibility.map
		,	oldData, newData, value
		;
		for (var itemPath in map) {
			oldData	= getBranch( itemPath );
			value	= oldData.branch[ oldData.key ]
			if (value !== undefined) {
				newData = getBranch( map[itemPath], true )
				newData.branch[ newData.key ] = value;
				delete oldData.branch[ oldData.key ];
			}
		}

		/**
		* @param {string}	path
		* @param {boolean=}	[create=false]	Create path if does not exist
		*/
		function getBranch (path, create) {
			var a = path.split(".") // split keys into array
			,	c = a.length - 1
			,	D = { branch: opts, key: a[c] } // init branch at top & set key (last item)
			,	i = 0, k, undef;
			for (; i<c; i++) { // skip the last key (data)
				k = a[i];
				if (D.branch[ k ] == undefined) { // child-key does not exist
					if (create) {
						D.branch = D.branch[ k ] = {}; // create child-branch
					}
					else // can't go any farther
						D.branch = {}; // branch is undefined
				}
				else
					D.branch = D.branch[ k ]; // get child-branch
			}
			return D;
		};
	}
	/**
	* @param {Object}	opts
	*/
,	renameAllOptions: function (opts) {
		var ren = $.layout.backwardCompatibility.renameOptions;
		// rename root (layout) options
		ren( opts );
		// rename 'defaults' to 'panes'
		if (opts.defaults) {
			if (typeof opts.panes !== "object")
				opts.panes = {};
			$.extend(true, opts.panes, opts.defaults);
			delete opts.defaults;
		}
		// rename options in the the options.panes key
		if (opts.panes) ren( opts.panes );
		// rename options inside *each pane key*, eg: options.west
		$.each($.layout.config.allPanes, function (i, pane) {
			if (opts[pane]) ren( opts[pane] );
		});	
		return opts;
	}
};



/*	============================================================
 *	BEGIN WIDGET: $( selector ).layout( {options} );
 *	============================================================
 */
$.fn.layout = function (opts) {
	var

	// local aliases to global data
	browser	= $.layout.browser
,	lang	= $.layout.language // internal alias
,	_c		= $.layout.config

	// local aliases to utlity methods
,	cssW	= $.layout.cssWidth
,	cssH	= $.layout.cssHeight
,	elDims	= $.layout.getElementDimensions
,	elCSS	= $.layout.getElementCSS

/**
 * options - populated by initOptions()
 */
,	options = $.extend(true, {}, $.layout.defaults)
,	effects	= options.effects = $.extend(true, {}, $.layout.effects)

/**
 * layout-state object
 */
,	state = {
		// generate unique ID to use for event.namespace so can unbind only events added by 'this layout'
		id:			"layout"+ $.now()	// code uses alias: sID
	,	initialized: false
	,	container:	{} // init all keys
	,	north:		{}
	,	south:		{}
	,	east:		{}
	,	west:		{}
	,	center:		{}
	}

/**
 * parent/child-layout pointers
 */
//,	hasParentLayout	= false	- exists ONLY as Instance.hasParentLayout
,	children = {
		north:		null
	,	south:		null
	,	east:		null
	,	west:		null
	,	center:		null
	}

/*
 * ###########################
 *  INTERNAL HELPER FUNCTIONS
 * ###########################
 */

	/**
	* Manages all internal timers
	*/
,	timer = {
		data:	{}
	,	set:	function (s, fn, ms) { timer.clear(s); timer.data[s] = setTimeout(fn, ms); }
	,	clear:	function (s) { var t=timer.data; if (t[s]) {clearTimeout(t[s]); delete t[s];} }
	}

,	_log = function (msg, popup) {
		$.layout.msg( msg, (popup && options.showErrorMessages) );
	}

	/**
	* Executes a Callback function after a trigger event, like resize, open or close
	*
	* @param {?string}				pane	This is passed only so we can pass the 'pane object' to the callback
	* @param {(string|function())}	v_fn	Accepts a function name, OR a comma-delimited array: [0]=function name, [1]=argument
	*/
,	_execCallback = function (pane, v_fn) {
		if (!v_fn) return;
		var fn;
		try {
			if (typeof v_fn === "function")
				fn = v_fn;	
			else if (!isStr(v_fn))
				return;
			else if (v_fn.match(/,/)) {
				// function name cannot contain a comma, so must be a function name AND a 'name' parameter
				var
					args = v_fn.split(",")
				,	fn = eval(args[0])
				;
				if (typeof fn=="function" && args.length > 1)
					return fn(args[1]); // pass the argument parsed from 'list'
			}
			else // just the name of an external function?
				fn = eval(v_fn);

			if ($.isFunction( fn )) {
				if (pane && $Ps[pane])
					// pass data: pane-name, pane-element, pane-state, pane-options, and layout-name
					return fn( pane, $Ps[pane], state[pane], options[pane], options.name );
				else // must be a layout/container callback - pass suitable info
					return fn( Instance, state, options, options.name );
			}
		}
		catch (ex) {}
	}

,	trigger = function (fnName, pane) {
		var o	= options
		,	fn	= pane && o[pane] ? o[pane][fnName] : o[fnName];
		if (fn) _execCallback(pane || null, fn);
	}

	/**
	* cure iframe display issues in IE & other browsers
	*/
,	_fixIframe = function (pane) {
		if (browser.mozilla) return; // skip FireFox - it auto-refreshes iframes onShow
		var $P = $Ps[pane];
		// if the 'pane' is an iframe, do it
		if (state[pane].tagName === "IFRAME")
			$P.css(_c.hidden).css(_c.visible); 
		else // ditto for any iframes INSIDE the pane
			$P.find('IFRAME').css(_c.hidden).css(_c.visible);
	}

	/**
	* @param  {string}		pane		Can accept ONLY a 'pane' (east, west, etc)
	* @param  {number=}		outerSize	(optional) Can pass a width, allowing calculations BEFORE element is resized
	* @return {number}		Returns the innerHeight/Width of el by subtracting padding and borders
	*/
,	cssSize = function (pane, outerSize) {
		var fn = _c[pane].dir=="horz" ? cssH : cssW;
		return fn($Ps[pane], outerSize);
	}

	/**
	* @param  {string}		pane		Can accept ONLY a 'pane' (east, west, etc)
	* @return {Object}		Returns hash of minWidth & minHeight
	*/
,	cssMinDims = function (pane) {
		// minWidth/Height means CSS width/height = 1px
		var
			$P	= $Ps[pane]
			dir	= _c[pane].dir
		,	d	= {
				minWidth:	1001 - cssW($P, 1000)
			,	minHeight:	1001 - cssH($P, 1000)
			}
		;
		if (dir === "horz") d.minSize = d.minHeight;
		if (dir === "vert") d.minSize = d.minWidth;
		return d;
	}

	// TODO: see if these methods can be made more useful...
	// TODO: *maybe* return cssW/H from these so caller can use this info

	/**
	* @param {(string|!Object)}		el
	* @param {number=}				outerWidth
	* @param {boolean=}				[autoHide=false]
	*/
,	setOuterWidth = function (el, outerWidth, autoHide) {
		var $E = el, w;
		if (isStr(el)) $E = $Ps[el]; // west
		else if (!el.jquery) $E = $(el);
		w = cssW($E, outerWidth);
		$E.css({ width: w });
		if (w > 0) {
			if (autoHide && $E.data('autoHidden') && $E.innerHeight() > 0) {
				$E.show().data('autoHidden', false);
				if (!browser.mozilla) // FireFox refreshes iframes - IE does not
					// make hidden, then visible to 'refresh' display after animation
					$E.css(_c.hidden).css(_c.visible);
			}
		}
		else if (autoHide && !$E.data('autoHidden'))
			$E.hide().data('autoHidden', true);
	}

	/**
	* @param {(string|!Object)}		el
	* @param {number=}				outerHeight
	* @param {boolean=}				[autoHide=false]
	*/
,	setOuterHeight = function (el, outerHeight, autoHide) {
		var $E = el, h;
		if (isStr(el)) $E = $Ps[el]; // west
		else if (!el.jquery) $E = $(el);
		h = cssH($E, outerHeight);
		$E.css({ height: h, visibility: "visible" }); // may have been 'hidden' by sizeContent
		if (h > 0 && $E.innerWidth() > 0) {
			if (autoHide && $E.data('autoHidden')) {
				$E.show().data('autoHidden', false);
				if (!browser.mozilla) // FireFox refreshes iframes - IE does not
					$E.css(_c.hidden).css(_c.visible);
			}
		}
		else if (autoHide && !$E.data('autoHidden'))
			$E.hide().data('autoHidden', true);
	}

	/**
	* @param {(string|!Object)}		el
	* @param {number=}				outerSize
	* @param {boolean=}				[autoHide=false]
	*/
,	setOuterSize = function (el, outerSize, autoHide) {
		if (_c[pane].dir=="horz") // pane = north or south
			setOuterHeight(el, outerSize, autoHide);
		else // pane = east or west
			setOuterWidth(el, outerSize, autoHide);
	}


	/**
	* Converts any 'size' params to a pixel/integer size, if not already
	* If 'auto' or a decimal/percentage is passed as 'size', a pixel-size is calculated
	*
	/**
	* @param  {string}				pane
	* @param  {(string|number)=}	size
	* @param  {string=}				[dir]
	* @return {number}
	*/
,	_parseSize = function (pane, size, dir) {
		if (!dir) dir = _c[pane].dir;

		if (isStr(size) && size.match(/%/))
			size = (size === '100%') ? -1 : parseInt(size, 10) / 100; // convert % to decimal

		if (size === 0)
			return 0;
		else if (size >= 1)
			return parseInt(size, 10);

		var o = options, avail = 0;
		if (dir=="horz") // north or south or center.minHeight
			avail = sC.innerHeight - ($Ps.north ? o.north.spacing_open : 0) - ($Ps.south ? o.south.spacing_open : 0);
		else if (dir=="vert") // east or west or center.minWidth
			avail = sC.innerWidth - ($Ps.west ? o.west.spacing_open : 0) - ($Ps.east ? o.east.spacing_open : 0);

		if (size === -1) // -1 == 100%
			return avail;
		else if (size > 0) // percentage, eg: .25
			return round(avail * size);
		else if (pane=="center")
			return 0;
		else { // size < 0 || size=='auto' || size==Missing || size==Invalid
			// auto-size the pane
			var
				$P	= $Ps[pane]
			,	dim	= (dir === "horz" ? "height" : "width")
			,	vis	= $.layout.showInvisibly($P) // show pane invisibly if hidden
			,	s	= $P.css(dim); // SAVE current size
			;
			$P.css(dim, "auto");
			size = (dim === "height") ? $P.outerHeight() : $P.outerWidth(); // MEASURE
			$P.css(dim, s).css(vis); // RESET size & visibility
			return size;
		}
	}

	/**
	* Calculates current 'size' (outer-width or outer-height) of a border-pane - optionally with 'pane-spacing' added
	*
	* @param  {(string|!Object)}	pane
	* @param  {boolean=}			[inclSpace=false]
	* @return {number}				Returns EITHER Width for east/west panes OR Height for north/south panes - adjusted for boxModel & browser
	*/
,	getPaneSize = function (pane, inclSpace) {
		var 
			$P	= $Ps[pane]
		,	o	= options[pane]
		,	s	= state[pane]
		,	oSp	= (inclSpace ? o.spacing_open : 0)
		,	cSp	= (inclSpace ? o.spacing_closed : 0)
		;
		if (!$P || s.isHidden)
			return 0;
		else if (s.isClosed || (s.isSliding && inclSpace))
			return cSp;
		else if (_c[pane].dir === "horz")
			return $P.outerHeight() + oSp;
		else // dir === "vert"
			return $P.outerWidth() + oSp;
	}

	/**
	* Calculate min/max pane dimensions and limits for resizing
	*
	* @param  {string}		pane
	* @param  {boolean=}	[slide=false]
	*/
,	setSizeLimits = function (pane, slide) {
		if (!isInitialized()) return;
		var 
			o				= options[pane]
		,	s				= state[pane]
		,	c				= _c[pane]
		,	dir				= c.dir
		,	side			= c.side.toLowerCase()
		,	type			= c.sizeType.toLowerCase()
		,	isSliding		= (slide != undefined ? slide : s.isSliding) // only open() passes 'slide' param
		,	$P				= $Ps[pane]
		,	paneSpacing		= o.spacing_open
		//	measure the pane on the *opposite side* from this pane
		,	altPane			= _c.oppositeEdge[pane]
		,	altS			= state[altPane]
		,	$altP			= $Ps[altPane]
		,	altPaneSize		= (!$altP || altS.isVisible===false || altS.isSliding ? 0 : (dir=="horz" ? $altP.outerHeight() : $altP.outerWidth()))
		,	altPaneSpacing	= ((!$altP || altS.isHidden ? 0 : options[altPane][ altS.isClosed !== false ? "spacing_closed" : "spacing_open" ]) || 0)
		//	limitSize prevents this pane from 'overlapping' opposite pane
		,	containerSize	= (dir=="horz" ? sC.innerHeight : sC.innerWidth)
		,	minCenterDims	= cssMinDims("center")
		,	minCenterSize	= dir=="horz" ? max(options.center.minHeight, minCenterDims.minHeight) : max(options.center.minWidth, minCenterDims.minWidth)
		//	if pane is 'sliding', then ignore center and alt-pane sizes - because 'overlays' them
		,	limitSize		= (containerSize - paneSpacing - (isSliding ? 0 : (_parseSize("center", minCenterSize, dir) + altPaneSize + altPaneSpacing)))
		,	minSize			= s.minSize = max( _parseSize(pane, o.minSize), cssMinDims(pane).minSize )
		,	maxSize			= s.maxSize = min( (o.maxSize ? _parseSize(pane, o.maxSize) : 100000), limitSize )
		,	r				= s.resizerPosition = {} // used to set resizing limits
		,	top				= sC.insetTop
		,	left			= sC.insetLeft
		,	W				= sC.innerWidth
		,	H				= sC.innerHeight
		,	rW				= o.spacing_open // subtract resizer-width to get top/left position for south/east
		;
		switch (pane) {
			case "north":	r.min = top + minSize;
							r.max = top + maxSize;
							break;
			case "west":	r.min = left + minSize;
							r.max = left + maxSize;
							break;
			case "south":	r.min = top + H - maxSize - rW;
							r.max = top + H - minSize - rW;
							break;
			case "east":	r.min = left + W - maxSize - rW;
							r.max = left + W - minSize - rW;
							break;
		};
	}

	/**
	* Returns data for setting the size/position of center pane. Also used to set Height for east/west panes
	*
	* @return JSON  Returns a hash of all dimensions: top, bottom, left, right, (outer) width and (outer) height
	*/
,	calcNewCenterPaneDims = function () {
		var d = {
			top:	getPaneSize("north", true) // true = include 'spacing' value for pane
		,	bottom:	getPaneSize("south", true)
		,	left:	getPaneSize("west", true)
		,	right:	getPaneSize("east", true)
		,	width:	0
		,	height:	0
		};

		// NOTE: sC = state.container
		// calc center-pane outer dimensions
		d.width		= sC.innerWidth - d.left - d.right;  // outerWidth
		d.height	= sC.innerHeight - d.bottom - d.top; // outerHeight
		// add the 'container border/padding' to get final positions relative to the container
		d.top		+= sC.insetTop;
		d.bottom	+= sC.insetBottom;
		d.left		+= sC.insetLeft;
		d.right		+= sC.insetRight;

		return d;
	}


	/**
	* @param {!Object}		el
	* @param {boolean=}		[allStates=false]
	*/
,	getHoverClasses = function (el, allStates) {
		var
			$El		= $(el)
		,	type	= $El.data("layoutRole")
		,	pane	= $El.data("layoutEdge")
		,	o		= options[pane]
		,	root	= o[type +"Class"]
		,	_pane	= "-"+ pane // eg: "-west"
		,	_open	= "-open"
		,	_closed	= "-closed"
		,	_slide	= "-sliding"
		,	_hover	= "-hover " // NOTE the trailing space
		,	_state	= $El.hasClass(root+_closed) ? _closed : _open
		,	_alt	= _state === _closed ? _open : _closed
		,	classes = (root+_hover) + (root+_pane+_hover) + (root+_state+_hover) + (root+_pane+_state+_hover)
		;
		if (allStates) // when 'removing' classes, also remove alternate-state classes
			classes += (root+_alt+_hover) + (root+_pane+_alt+_hover);

		if (type=="resizer" && $El.hasClass(root+_slide))
			classes += (root+_slide+_hover) + (root+_pane+_slide+_hover);

		return $.trim(classes);
	}
,	addHover	= function (evt, el) {
		var $E = $(el || this);
		if (evt && $E.data("layoutRole") === "toggler")
			evt.stopPropagation(); // prevent triggering 'slide' on Resizer-bar
		$E.addClass( getHoverClasses($E) );
	}
,	removeHover	= function (evt, el) {
		var $E = $(el || this);
		$E.removeClass( getHoverClasses($E, true) );
	}

,	onResizerEnter	= function (evt) { // ALSO called by toggler.mouseenter
		if ($.fn.disableSelection)
			$("body").disableSelection();
	}
,	onResizerLeave	= function (evt, el) {
		var
			e = el || this // el is only passed when called by the timer
		,	pane = $(e).data("layoutEdge")
		,	name = pane +"ResizerLeave"
		;
		timer.clear(pane+"_openSlider"); // cancel slideOpen timer, if set
		timer.clear(name); // cancel enableSelection timer - may re/set below
		// this method calls itself on a timer because it needs to allow
		// enough time for dragging to kick-in and set the isResizing flag
		// dragging has a 100ms delay set, so this delay must be >100
		if (!el) // 1st call - mouseleave event
			timer.set(name, function(){ onResizerLeave(evt, e); }, 200);
		// if user is resizing, then dragStop will enableSelection(), so can skip it here
		else if (!state[pane].isResizing && $.fn.enableSelection) // 2nd call - by timer
			$("body").enableSelection();
	}

/*
 * ###########################
 *   INITIALIZATION METHODS
 * ###########################
 */

	/**
	* Initialize the layout - called automatically whenever an instance of layout is created
	*
	* @see  none - triggered onInit
	* @return  mixed	true = fully initialized | false = panes not initialized (yet) | 'cancel' = abort
	*/
,	_create = function () {
		// initialize config/options
		initOptions();
		var o = options;

		// TEMP state so isInitialized returns true during init process
		state.creatingLayout = true;

		// init plugins for this layout, if there are any (eg: stateManagement)
		runPluginCallbacks( Instance, $.layout.onCreate );

		// options & state have been initialized, so now run beforeLoad callback
		// onload will CANCEL layout creation if it returns false
		if (false === _execCallback(null, o.onload_start))
			return 'cancel';

		// initialize the container element
		_initContainer();

		// bind hotkey function - keyDown - if required
		initHotkeys();

		// bind window.onunload
		$(window).bind("unload."+ sID, unload);

		// init plugins for this layout, if there are any (eg: customButtons)
		runPluginCallbacks( Instance, $.layout.onLoad );

		// if this layout's container is another layout's pane, then set child/parent pointers
		var parent = $N.data("parentLayout");
		if (parent) {
			Instance.hasParentLayout = true;
			var pane = $N.data("layoutEdge"); // container's pane-name in parent-layout
			// set pointers to THIS child-layout in parent-layout
			// NOTE: parent.PANE.child is an ALIAS to parent.children.PANE
			parent[pane].child = parent.children[pane] = Instance;
		}

		// if layout elements are hidden, then layout WILL NOT complete initialization!
		// initLayoutElements will set initialized=true and run the onload callback IF successful
		if (o.initPanes) _initLayoutElements();

		delete state.creatingLayout;

		return state.initialized;
	}

	/**
	* Initialize the layout IF not already
	*
	* @see  All methods in Instance run this test
	* @return  boolean	true = layoutElements have been initialized | false = panes are not initialized (yet)
	*/
,	isInitialized = function () {
		if (state.initialized || state.creatingLayout) return true;	// already initialized
		else return _initLayoutElements();	// try to init panes NOW
	}

	/**
	* Initialize the layout - called automatically whenever an instance of layout is created
	*
	* @see  _create() & isInitialized
	* @return  An object pointer to the instance created
	*/
,	_initLayoutElements = function (retry) {
		// initialize config/options
		var o = options;

		// CANNOT init panes inside a hidden container!
		if (!$N.is(":visible")) {
			// handle Chrome bug where popup window 'has no height'
			// if layout is BODY element, try again in 50ms
			// SEE: http://layout.jquery-dev.net/samples/test_popup_window.html
			if ( !retry && browser.webkit && $N[0].tagName === "BODY" )
				setTimeout(function(){ _initLayoutElements(true); }, 50);
			return false;
		}

		// a center pane is required, so make sure it exists
		if (!getPane("center").length) {
			_log( lang.errCenterPaneMissing, true );
			return false;
		}

		// TEMP state so isInitialized returns true during init process
		state.creatingLayout = true;

		// update Container dims
		$.extend(sC, elDims( $N ));

		// check to see if this layout 'nested' inside a pane
		if ($N.data("layoutRole") === "pane")
			o.resizeWithWindow = false;

 		// initialize all layout elements
		initPanes();	// size & position panes - calls initHandles() - which calls initResizable()

		if (o.scrollToBookmarkOnLoad) {
			var l = self.location;
			if (l.hash) l.replace( l.hash ); // scrollTo Bookmark
		}

		// bind resizeAll() for 'this layout instance' to window.resize event
		if (o.resizeWithWindow)
			$(window).bind("resize."+ sID, windowResize);

		delete state.creatingLayout;
		state.initialized = true;

		// init plugins for this layout, if there are any
		runPluginCallbacks( Instance, $.layout.onReady );

		// now run the onload callback, if exists
		_execCallback(null, o.onload_end || o.onload);

		return true; // elements initialized successfully
	}

	/**
	* Initialize nested layouts - called when _initLayoutElements completes
	*
	* NOT CURRENTLY USED
	*
	* @see _initLayoutElements
	* @return  An object pointer to the instance created
	*/
,	_initChildLayouts = function () {
		$.each(_c.allPanes, function (idx, pane) {
			if (options[pane].initChildLayout)
				createChildLayout( pane );
		});
	}

	/**
	* Initialize nested layouts for a specific pane - can optionally pass layout-options
	*
	* @see _initChildLayouts
	* @param {string}	pane		The pane being opened, ie: north, south, east, or west
	* @param {Object=}	[opts]		Layout-options - if passed, will OVERRRIDE options[pane].childOptions
	* @return  An object pointer to the layout instance created - or null
	*/
,	createChildLayout = function (pane, opts) {
		var	$P	= $Ps[pane]
		,	o	= opts || options[pane].childOptions
		,	C	= children
		,	d	= "layout"
		//	see if a child-layout ALREADY exists on this element
		,	L	= $P ? (C[pane] = $P.data(d) || null) : false
		;
		// if no layout exists, but childOptions are set, try to create the layout now
		if (!L && $P && o)
			L = C[pane] = $P.layout(o) || null;
		if (L)
			L.hasParentLayout = true;	// set parent-flag in child - DO NOT set pointer or else have infinite recursion!
		Instance[pane].child = C[pane];	// set pane-object pointer, even if null
	}

,	windowResize = function () {
		var delay = Number(options.resizeWithWindowDelay);
		if (delay < 10) delay = 100; // MUST have a delay!
		// resizing uses a delay-loop because the resize event fires repeatly - except in FF, but delay anyway
		timer.clear("winResize"); // if already running
		timer.set("winResize", function(){
			timer.clear("winResize");
			timer.clear("winResizeRepeater");
			var dims = elDims( $N );
			// only trigger resizeAll() if container has changed size
			if (dims.innerWidth !== sC.innerWidth || dims.innerHeight !== sC.innerHeight)
				resizeAll();
		}, delay);
		// ALSO set fixed-delay timer, if not already running
		if (!timer.data["winResizeRepeater"]) setWindowResizeRepeater();
	}

,	setWindowResizeRepeater = function () {
		var delay = Number(options.resizeWithWindowMaxDelay);
		if (delay > 0)
			timer.set("winResizeRepeater", function(){ setWindowResizeRepeater(); resizeAll(); }, delay);
	}

,	unload = function () {
		var o = options;

		_execCallback(null, o.onunload_start);

		// trigger plugin callabacks for this layout (eg: stateManagement)
		runPluginCallbacks( Instance, $.layout.onUnload );

		_execCallback(null, o.onunload_end || o.onunload);
	}

	/**
	* Validate and initialize container CSS and events
	*
	* @see  _create()
	*/
,	_initContainer = function () {
		var
			N		= $N[0]
		,	tag		= sC.tagName = N.tagName
		,	id		= sC.id = N.id
		,	cls		= sC.className = N.className
		,	o		= options
		,	name	= o.name
		,	fullPage= (tag === "BODY")
		,	props	= "overflow,position,margin,padding,border"
		,	CSS		= {}
		,	hid		= "hidden" // used A LOT!
		;
		// sC -> state.container
		sC.selector = $N.selector.split(".slice")[0];
		sC.ref		= (o.name ? o.name +' layout / ' : '') + tag + (id ? "#"+id : cls ? '.['+cls+']' : ''); // used in messages

		$N	.data("layout", Instance)
			.data("layoutContainer", sID)	// unique identifier for internal use
			.addClass(o.containerClass)
		;

		// SAVE original container CSS for use in destroy()
		var css = "layoutCSS";
		if (!$N.data(css)) {
			// handle props like overflow different for BODY & HTML - has 'system default' values
			if (fullPage) {
				CSS = $.extend( elCSS($N, props), {
					height:		$N.css("height")
				,	overflow:	$N.css("overflow")
				,	overflowX:	$N.css("overflowX")
				,	overflowY:	$N.css("overflowY")
				});
				// ALSO SAVE <HTML> CSS
				var $H = $("html");
				$H.data(css, {
					height:		"auto" // FF would return a fixed px-size!
				,	overflow:	$H.css("overflow")
				,	overflowX:	$H.css("overflowX")
				,	overflowY:	$H.css("overflowY")
				});
			}
			else // handle props normally for non-body elements
				CSS = elCSS($N, props+",top,bottom,left,right,width,height,overflow,overflowX,overflowY");

			$N.data(css, CSS);
		}

		try { // format html/body if this is a full page layout
			if (fullPage) {
				$("html").css({
					height:		"100%"
				,	overflow:	hid
				,	overflowX:	hid
				,	overflowY:	hid
				});
				$("body").css({
					position:	"relative"
				,	height:		"100%"
				,	overflow:	hid
				,	overflowX:	hid
				,	overflowY:	hid
				,	margin:		0
				,	padding:	0		// TODO: test whether body-padding could be handled?
				,	border:		"none"	// a body-border creates problems because it cannot be measured!
				});

				// set current layout-container dimensions
				$.extend(sC, elDims( $N ));
			}
			else { // set required CSS for overflow and position
				// ENSURE container will not 'scroll'
				CSS = { overflow: hid, overflowX: hid, overflowY: hid }
				var
					p = $N.css("position")
				,	h = $N.css("height")
				;
				// if this is a NESTED layout, then container/outer-pane ALREADY has position and height
				if (!$N.data("layoutRole")) {
					if (!p || !p.match(/fixed|absolute|relative/))
						CSS.position = "relative"; // container MUST have a 'position'
					/*
					if (!h || h=="auto")
						CSS.height = "100%"; // container MUST have a 'height'
					*/
				}
				$N.css( CSS );

				// set current layout-container dimensions
				if ( $N.is(":visible") ) {
					$.extend(sC, elDims( $N ));
					if (o.showErrorMessages && sC.innerHeight < 1)
						_log( lang.errContainerHeight.replace(/CONTAINER/, sC.ref), true );
				}
			}
		} catch (ex) {}
	}

	/**
	* Bind layout hotkeys - if options enabled
	*
	* @see  _create() and addPane()
	* @param {string=}	[panes=""]	The edge(s) to process
	*/
,	initHotkeys = function (panes) {
		panes = panes ? panes.split(",") : _c.borderPanes;
		// bind keyDown to capture hotkeys, if option enabled for ANY pane
		$.each(panes, function (i, pane) {
			var o = options[pane];
			if (o.enableCursorHotkey || o.customHotkey) {
				$(document).bind("keydown."+ sID, keyDown); // only need to bind this ONCE
				return false; // BREAK - binding was done
			}
		});
	}

	/**
	* Build final OPTIONS data
	*
	* @see  _create()
	*/
,	initOptions = function () {
		var data, d, pane, key, val, i, c, o;

		// reprocess user's layout-options to have correct options sub-key structure
		opts = $.layout.transformData( opts ); // panes = default subkey

		// auto-rename old options for backward compatibility
		opts = $.layout.backwardCompatibility.renameAllOptions( opts );

		// if user-options has 'panes' key (pane-defaults), process it...
		if (!$.isEmptyObject(opts.panes)) {
			// REMOVE any pane-defaults that MUST be set per-pane
			data = $.layout.optionsMap.noDefault;
			for (i=0, c=data.length; i<c; i++) {
				key = data[i];
				delete opts.panes[key]; // OK if does not exist
			}
			// REMOVE any layout-options specified under opts.panes
			data = $.layout.optionsMap.layout;
			for (i=0, c=data.length; i<c; i++) {
				key = data[i];
				delete opts.panes[key]; // OK if does not exist
			}
		}

		// MOVE any NON-layout-options to opts.panes
		data = $.layout.optionsMap.layout;
		var rootKeys = $.layout.config.optionRootKeys;
		for (key in opts) {
			val = opts[key];
			if ($.inArray(key, rootKeys) < 0 && $.inArray(key, data) < 0) {
				if (!opts.panes[key])
					opts.panes[key] = $.isPlainObject(val) ? $.extend(true, {}, val) : val;
				delete opts[key]
			}
		}

		// START by updating ALL options from opts
		$.extend(true, options, opts);

		// CREATE final options (and config) for EACH pane
		$.each(_c.allPanes, function (i, pane) {

			// apply 'pane-defaults' to CONFIG.[PANE]
			_c[pane] = $.extend( true, {}, _c.panes, _c[pane] );

			d = options.panes;
			o = options[pane];

			// center-pane uses SOME keys in defaults.panes branch
			if (pane === 'center') {
				// ONLY copy keys from opts.panes listed in: $.layout.optionsMap.center
				data = $.layout.optionsMap.center;		// list of 'center-pane keys'
				for (i=0, c=data.length; i<c; i++) {	// loop the list...
					key = data[i];
					// only need to use pane-default if pane-specific value not set
					if (!opts.center[key] && (opts.panes[key] || !o[key]))
						o[key] = d[key]; // pane-default
				}
			}
			else {
				// border-panes use ALL keys in defaults.panes branch
				o = options[pane] = $.extend({}, d, o); // re-apply pane-specific opts AFTER pane-defaults
				createFxOptions( pane );
				// ensure all border-pane-specific base-classes exist
				if (!o.resizerClass)	o.resizerClass	= "ui-layout-resizer";
				if (!o.togglerClass)	o.togglerClass	= "ui-layout-toggler";
			}
			// ensure we have base pane-class (ALL panes)
			if (!o.paneClass) o.paneClass = "ui-layout-pane";
		});

		// update options.zIndexes if a zIndex-option specified
		var zo	= opts.zIndex
		,	z	= options.zIndexes;
		if (zo > 0) {
			z.pane_normal		= zo;
			z.content_mask		= max(zo+1, z.content_mask);	// MIN = +1
			z.resizer_normal	= max(zo+2, z.resizer_normal);	// MIN = +2
		}

		function createFxOptions ( pane ) {
			var	o = options[pane]
			,	d = options.panes;
			// ensure fxSettings key to avoid errors
			if (!o.fxSettings) o.fxSettings = {};
			if (!d.fxSettings) d.fxSettings = {};

			$.each(["_open","_close","_size"], function (i,n) { 
				var
					sName		= "fxName"+ n
				,	sSpeed		= "fxSpeed"+ n
				,	sSettings	= "fxSettings"+ n
					// recalculate fxName according to specificity rules
				,	fxName = o[sName] =
						o[sName]	// options.west.fxName_open
					||	d[sName]	// options.panes.fxName_open
					||	o.fxName	// options.west.fxName
					||	d.fxName	// options.panes.fxName
					||	"none"		// MEANS $.layout.defaults.panes.fxName == "" || false || null || 0
				;
				// validate fxName to ensure is valid effect - MUST have effect-config data in options.effects
				if (fxName === "none" || !$.effects || !$.effects[fxName] || !options.effects[fxName])
					fxName = o[sName] = "none"; // effect not loaded OR unrecognized fxName

				// set vars for effects subkeys to simplify logic
				var	fx		= options.effects[fxName] || {}	// effects.slide
				,	fx_all	= fx.all	|| null				// effects.slide.all
				,	fx_pane	= fx[pane]	|| null				// effects.slide.west
				;
				// create fxSpeed[_open|_close|_size]
				o[sSpeed] =
					o[sSpeed]				// options.west.fxSpeed_open
				||	d[sSpeed]				// options.west.fxSpeed_open
				||	o.fxSpeed				// options.west.fxSpeed
				||	d.fxSpeed				// options.panes.fxSpeed
				||	null					// DEFAULT - let fxSetting.duration control speed
				;
				// create fxSettings[_open|_close|_size]
				o[sSettings] = $.extend(
					{}
				,	fx_all					// effects.slide.all
				,	fx_pane					// effects.slide.west
				,	d.fxSettings			// options.panes.fxSettings
				,	o.fxSettings			// options.west.fxSettings
				,	d[sSettings]			// options.panes.fxSettings_open
				,	o[sSettings]			// options.west.fxSettings_open
				);
			});

			// DONE creating action-specific-settings for this pane,
			// so DELETE generic options - are no longer meaningful
			delete o.fxName;
			delete o.fxSpeed;
			delete o.fxSettings;
		}

		// DELETE 'panes' key now that we are done - values were copied to EACH pane
		delete options.panes;
	}

	/**
	* Initialize module objects, styling, size and position for all panes
	*
	* @see  _initElements()
	* @param {string}	pane		The pane to process
	*/
,	getPane = function (pane) {
		var sel = options[pane].paneSelector
		if (sel.substr(0,1)==="#") // ID selector
			// NOTE: elements selected 'by ID' DO NOT have to be 'children'
			return $N.find(sel).eq(0);
		else { // class or other selector
			var $P = $N.children(sel).eq(0);
			// look for the pane nested inside a 'form' element
			return $P.length ? $P : $N.children("form:first").children(sel).eq(0);
		}
	}

,	initPanes = function () {
		// NOTE: do north & south FIRST so we can measure their height - do center LAST
		$.each(_c.allPanes, function (idx, pane) {
			addPane( pane, true );
		});

		// init the pane-handles NOW in case we have to hide or close the pane below
		initHandles();

		// now that all panes have been initialized and initially-sized,
		// make sure there is really enough space available for each pane
		$.each(_c.borderPanes, function (i, pane) {
			if ($Ps[pane] && state[pane].isVisible) { // pane is OPEN
				setSizeLimits(pane);
				makePaneFit(pane); // pane may be Closed, Hidden or Resized by makePaneFit()
			}
		});
		// size center-pane AGAIN in case we 'closed' a border-pane in loop above
		sizeMidPanes("center");

		//	Chrome/Webkit sometimes fires callbacks BEFORE it completes resizing!
		//	Before RC30.3, there was a 10ms delay here, but that caused layout 
		//	to load asynchrously, which is BAD, so try skipping delay for now

		// process pane contents and callbacks, and init/resize child-layout if exists
		$.each(_c.allPanes, function (i, pane) {
			var o = options[pane];
			if ($Ps[pane]) {
				if (state[pane].isVisible) { // pane is OPEN
					sizeContent(pane);
					// trigger pane.onResize if triggerEventsOnLoad = true
					if (o.triggerEventsOnLoad)
						_execCallback(pane, o.onresize_end || o.onresize);
					// resize child - IF inner-layout already exists (created before this layout)
					resizeChildLayout(pane);
				}
				// init childLayout - even if pane is not visible
				if (o.initChildLayout && o.childOptions)
					createChildLayout(pane);
			}
		});
	}

	/**
	* Add a pane to the layout - subroutine of initPanes()
	*
	* @see  initPanes()
	* @param {string}	pane			The pane to process
	* @param {boolean=}	[force=false]	Size content after init
	*/
,	addPane = function (pane, force) {
		if (!force && !isInitialized()) return;
		var
			o		= options[pane]
		,	s		= state[pane]
		,	c		= _c[pane]
		,	fx		= s.fx
		,	dir		= c.dir
		,	spacing	= o.spacing_open || 0
		,	isCenter = (pane === "center")
		,	CSS		= {}
		,	$P		= $Ps[pane]
		,	size, minSize, maxSize
		;

		// if pane-pointer already exists, remove the old one first
		if ($P)
			removePane( pane, false, true, false );
		else
			$Cs[pane] = false; // init

		$P = $Ps[pane] = getPane(pane);
		if (!$P.length) {
			$Ps[pane] = false; // logic
			return;
		}

		// SAVE original Pane CSS
		if (!$P.data("layoutCSS")) {
			var props = "position,top,left,bottom,right,width,height,overflow,zIndex,display,backgroundColor,padding,margin,border";
			$P.data("layoutCSS", elCSS($P, props));
		}

		// add classes, attributes & events
		$P	.data("parentLayout", Instance)
			.data("layoutRole", "pane")
			.data("layoutEdge", pane)
			.css(c.cssReq).css("zIndex", options.zIndexes.pane_normal)
			.css(o.applyDemoStyles ? c.cssDemo : {}) // demo styles
			.addClass( o.paneClass +" "+ o.paneClass+"-"+pane ) // default = "ui-layout-pane ui-layout-pane-west" - may be a dupe of 'paneSelector'
			.bind("mouseenter."+ sID, addHover )
			.bind("mouseleave."+ sID, removeHover );

		// create alias for pane data in Instance - initHandles will add more
		Instance[pane] = { name: pane, pane: $Ps[pane], options: options[pane], state: state[pane], child: children[pane] };

		// see if this pane has a 'scrolling-content element'
		initContent(pane, false); // false = do NOT sizeContent() - called later

		if (!isCenter) {
			// call _parseSize AFTER applying pane classes & styles - but before making visible (if hidden)
			// if o.size is auto or not valid, then MEASURE the pane and use that as its 'size'
			size	= s.size = _parseSize(pane, o.size);
			minSize	= _parseSize(pane,o.minSize) || 1;
			maxSize	= _parseSize(pane,o.maxSize) || 100000;
			if (size > 0) size = max(min(size, maxSize), minSize);

			// state for border-panes
			s.isClosed  = false; // true = pane is closed
			s.isSliding = false; // true = pane is currently open by 'sliding' over adjacent panes
			s.isResizing= false; // true = pane is in process of being resized
			s.isHidden	= false; // true = pane is hidden - no spacing, resizer or toggler is visible!

			// array for 'pin buttons' whose classNames are auto-updated on pane-open/-close
			if (!s.pins) s.pins = [];
		}
		//	states common to ALL panes
		s.tagName	= $P[0].tagName;
		s.edge		= pane;		// useful if pane is (or about to be) 'swapped' - easy find out where it is (or is going)
		s.noRoom	= false;	// true = pane 'automatically' hidden due to insufficient room - will unhide automatically
		s.isVisible	= true;		// false = pane is invisible - closed OR hidden - simplify logic

		// set css-position to account for container borders & padding
		switch (pane) {
			case "north": 	CSS.top 	= sC.insetTop;
							CSS.left 	= sC.insetLeft;
							CSS.right	= sC.insetRight;
							break;
			case "south": 	CSS.bottom	= sC.insetBottom;
							CSS.left 	= sC.insetLeft;
							CSS.right 	= sC.insetRight;
							break;
			case "west": 	CSS.left 	= sC.insetLeft; // top, bottom & height set by sizeMidPanes()
							break;
			case "east": 	CSS.right 	= sC.insetRight; // ditto
							break;
			case "center":	// top, left, width & height set by sizeMidPanes()
		}

		if (dir === "horz") // north or south pane
			CSS.height = cssH($P, size);
		else if (dir === "vert") // east or west pane
			CSS.width = cssW($P, size);
		//else if (isCenter) {}

		$P.css(CSS); // apply size -- top, bottom & height will be set by sizeMidPanes
		if (dir != "horz") sizeMidPanes(pane, true); // true = skipCallback

		// close or hide the pane if specified in settings
		if (o.initClosed && o.closable && !o.initHidden)
			close(pane, true, true); // true, true = force, noAnimation
		else if (o.initHidden || o.initClosed)
			hide(pane); // will be completely invisible - no resizer or spacing
		else if (!s.noRoom)
			// make the pane visible - in case was initially hidden
			$P.css("display","block");
		// ELSE setAsOpen() - called later by initHandles()

		// RESET visibility now - pane will appear IF display:block
		$P.css("visibility","visible");

		// check option for auto-handling of pop-ups & drop-downs
		if (o.showOverflowOnHover)
			$P.hover( allowOverflow, resetOverflow );

		// if adding a pane AFTER initialization, then...
		if (state.initialized) {
			initHandles( pane );
			initHotkeys( pane );
			resizeAll(); // will sizeContent if pane is visible
			if (s.isVisible) { // pane is OPEN
				if (o.triggerEventsOnLoad)
					_execCallback(pane, o.onresize_end || o.onresize); 
				resizeChildLayout(pane);
			}
			if (o.initChildLayout && o.childOptions)
				createChildLayout(pane);
		}
	}

	/**
	* Initialize module objects, styling, size and position for all resize bars and toggler buttons
	*
	* @see  _create()
	* @param {string=}	[panes=""]	The edge(s) to process
	*/
,	initHandles = function (panes) {
		panes = panes ? panes.split(",") : _c.borderPanes;

		// create toggler DIVs for each pane, and set object pointers for them, eg: $R.north = north toggler DIV
		$.each(panes, function (i, pane) {
			var $P		= $Ps[pane];
			$Rs[pane]	= false; // INIT
			$Ts[pane]	= false;
			if (!$P) return; // pane does not exist - skip

			var 
				o		= options[pane]
			,	s		= state[pane]
			,	c		= _c[pane]
			,	rClass	= o.resizerClass
			,	tClass	= o.togglerClass
			,	side	= c.side.toLowerCase()
			,	spacing	= (s.isVisible ? o.spacing_open : o.spacing_closed)
			,	_pane	= "-"+ pane // used for classNames
			,	_state	= (s.isVisible ? "-open" : "-closed") // used for classNames
			,	I		= Instance[pane]
				// INIT RESIZER BAR
			,	$R		= I.resizer = $Rs[pane] = $("<div></div>")
				// INIT TOGGLER BUTTON
			,	$T		= I.toggler = (o.closable ? $Ts[pane] = $("<div></div>") : false)
			;

			//if (s.isVisible && o.resizable) ... handled by initResizable
			if (!s.isVisible && o.slidable)
				$R.attr("title", o.sliderTip).css("cursor", o.sliderCursor);

			$R	// if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "paneLeft-resizer"
				.attr("id", (o.paneSelector.substr(0,1)=="#" ? o.paneSelector.substr(1) + "-resizer" : ""))
				.data("parentLayout", Instance)
				.data("layoutRole", "resizer")
				.data("layoutEdge", pane)
				.css(_c.resizers.cssReq).css("zIndex", options.zIndexes.resizer_normal)
				.css(o.applyDemoStyles ? _c.resizers.cssDemo : {}) // add demo styles
				.addClass(rClass +" "+ rClass+_pane)
				.hover(addHover, removeHover) // ALWAYS add hover-classes, even if resizing is not enabled - handle with CSS instead
				.hover(onResizerEnter, onResizerLeave) // ALWAYS NEED resizer.mouseleave to balance toggler.mouseenter
				.appendTo($N) // append DIV to container
			;

			if ($T) {
				$T	// if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "#paneLeft-toggler"
					.attr("id", (o.paneSelector.substr(0,1)=="#" ? o.paneSelector.substr(1) + "-toggler" : ""))
					.data("parentLayout", Instance)
					.data("layoutRole", "toggler")
					.data("layoutEdge", pane)
					.css(_c.togglers.cssReq) // add base/required styles
					.css(o.applyDemoStyles ? _c.togglers.cssDemo : {}) // add demo styles
					.addClass(tClass +" "+ tClass+_pane)
					.hover(addHover, removeHover) // ALWAYS add hover-classes, even if toggling is not enabled - handle with CSS instead
					.bind("mouseenter", onResizerEnter) // NEED toggler.mouseenter because mouseenter MAY NOT fire on resizer
					.appendTo($R) // append SPAN to resizer DIV
				;
				// ADD INNER-SPANS TO TOGGLER
				if (o.togglerContent_open) // ui-layout-open
					$("<span>"+ o.togglerContent_open +"</span>")
						.data("layoutRole", "togglerContent")
						.data("layoutEdge", pane)
						.addClass("content content-open")
						.css("display","none")
						.appendTo( $T )
						//.hover( addHover, removeHover ) // use ui-layout-toggler-west-hover .content-open instead!
					;
				if (o.togglerContent_closed) // ui-layout-closed
					$("<span>"+ o.togglerContent_closed +"</span>")
						.data("layoutRole", "togglerContent")
						.data("layoutEdge", pane)
						.addClass("content content-closed")
						.css("display","none")
						.appendTo( $T )
						//.hover( addHover, removeHover ) // use ui-layout-toggler-west-hover .content-closed instead!
					;
				// ADD TOGGLER.click/.hover
				enableClosable(pane);
			}

			// add Draggable events
			initResizable(pane);

			// ADD CLASSNAMES & SLIDE-BINDINGS - eg: class="resizer resizer-west resizer-open"
			if (s.isVisible)
				setAsOpen(pane);	// onOpen will be called, but NOT onResize
			else {
				setAsClosed(pane);	// onClose will be called
				bindStartSlidingEvent(pane, true); // will enable events IF option is set
			}

		});

		// SET ALL HANDLE DIMENSIONS
		sizeHandles();
	}


	/**
	* Initialize scrolling ui-layout-content div - if exists
	*
	* @see  initPane() - or externally after an Ajax injection
	* @param {string}	[pane]			The pane to process
	* @param {boolean=}	[resize=true]	Size content after init
	*/
,	initContent = function (pane, resize) {
		if (!isInitialized()) return;
		var 
			o	= options[pane]
		,	sel	= o.contentSelector
		,	I	= Instance[pane]
		,	$P	= $Ps[pane]
		,	$C
		;
		if (sel) $C = I.content = $Cs[pane] = (o.findNestedContent)
			? $P.find(sel).eq(0) // match 1-element only
			: $P.children(sel).eq(0)
		;
		if ($C && $C.length) {
			$C.data("layoutRole", "content");
			// SAVE original Pane CSS
			if (!$C.data("layoutCSS"))
				$C.data("layoutCSS", elCSS($C, "height"));
			$C.css( _c.content.cssReq );
			if (o.applyDemoStyles) {
				$C.css( _c.content.cssDemo ); // add padding & overflow: auto to content-div
				$P.css( _c.content.cssDemoPane ); // REMOVE padding/scrolling from pane
			}
			state[pane].content = {}; // init content state
			if (resize !== false) sizeContent(pane);
			// sizeContent() is called AFTER init of all elements
		}
		else
			I.content = $Cs[pane] = false;
	}


	/**
	* Add resize-bars to all panes that specify it in options
	* -dependancy: $.fn.resizable - will skip if not found
	*
	* @see  _create()
	* @param {string=}	[panes=""]	The edge(s) to process
	*/
,	initResizable = function (panes) {
		var	draggingAvailable = $.layout.plugins.draggable
		,	side // set in start()
		;
		panes = panes ? panes.split(",") : _c.borderPanes;

		$.each(panes, function (idx, pane) {
			var o = options[pane];
			if (!draggingAvailable || !$Ps[pane] || !o.resizable) {
				o.resizable = false;
				return true; // skip to next
			}

			var s		= state[pane]
			,	z		= options.zIndexes
			,	c		= _c[pane]
			,	side	= c.dir=="horz" ? "top" : "left"
			,	opEdge	= _c.oppositeEdge[pane]
			,	masks	=  pane +",center,"+ opEdge + (c.dir=="horz" ? ",west,east" : "")
			,	$P 		= $Ps[pane]
			,	$R		= $Rs[pane]
			,	base	= o.resizerClass
			,	lastPos	= 0 // used when live-resizing
			,	r, live // set in start because may change
			//	'drag' classes are applied to the ORIGINAL resizer-bar while dragging is in process
			,	resizerClass		= base+"-drag"				// resizer-drag
			,	resizerPaneClass	= base+"-"+pane+"-drag"		// resizer-north-drag
			//	'helper' class is applied to the CLONED resizer-bar while it is being dragged
			,	helperClass			= base+"-dragging"			// resizer-dragging
			,	helperPaneClass		= base+"-"+pane+"-dragging" // resizer-north-dragging
			,	helperLimitClass	= base+"-dragging-limit"	// resizer-drag
			,	helperPaneLimitClass = base+"-"+pane+"-dragging-limit"	// resizer-north-drag
			,	helperClassesSet	= false 					// logic var
			;

			if (!s.isClosed)
				$R.attr("title", o.resizerTip)
				  .css("cursor", o.resizerCursor); // n-resize, s-resize, etc

			$R.draggable({
				containment:	$N[0] // limit resizing to layout container
			,	axis:			(c.dir=="horz" ? "y" : "x") // limit resizing to horz or vert axis
			,	delay:			0
			,	distance:		1
			,	grid:			o.resizingGrid
			//	basic format for helper - style it using class: .ui-draggable-dragging
			,	helper:			"clone"
			,	opacity:		o.resizerDragOpacity
			,	addClasses:		false // avoid ui-state-disabled class when disabled
			//,	iframeFix:		o.draggableIframeFix // TODO: consider using when bug is fixed
			,	zIndex:			z.resizer_drag

			,	start: function (e, ui) {
					// REFRESH options & state pointers in case we used swapPanes
					o = options[pane];
					s = state[pane];
					// re-read options
					live = o.livePaneResizing;

					// ondrag_start callback - will CANCEL hide if returns false
					// TODO: dragging CANNOT be cancelled like this, so see if there is a way?
					if (false === _execCallback(pane, o.ondrag_start)) return false;

					s.isResizing	= true; // prevent pane from closing while resizing
					timer.clear(pane+"_closeSlider"); // just in case already triggered

					// SET RESIZER LIMITS - used in drag()
					setSizeLimits(pane); // update pane/resizer state
					r = s.resizerPosition;
					lastPos = ui.position[ side ]

					$R.addClass( resizerClass +" "+ resizerPaneClass ); // add drag classes
					helperClassesSet = false; // reset logic var - see drag()

					// DISABLE TEXT SELECTION (probably already done by resizer.mouseOver)
					$('body').disableSelection(); 

					// MASK PANES CONTAINING IFRAMES, APPLETS OR OTHER TROUBLESOME ELEMENTS
					showMasks( masks );
				}

			,	drag: function (e, ui) {
					if (!helperClassesSet) { // can only add classes after clone has been added to the DOM
						//$(".ui-draggable-dragging")
						ui.helper
							.addClass( helperClass +" "+ helperPaneClass ) // add helper classes
							.css({ right: "auto", bottom: "auto" })	// fix dir="rtl" issue
							.children().css("visibility","hidden")	// hide toggler inside dragged resizer-bar
						;
						helperClassesSet = true;
						// draggable bug!? RE-SET zIndex to prevent E/W resize-bar showing through N/S pane!
						if (s.isSliding) $Ps[pane].css("zIndex", z.pane_sliding);
					}
					// CONTAIN RESIZER-BAR TO RESIZING LIMITS
					var limit = 0;
					if (ui.position[side] < r.min) {
						ui.position[side] = r.min;
						limit = -1;
					}
					else if (ui.position[side] > r.max) {
						ui.position[side] = r.max;
						limit = 1;
					}
					// ADD/REMOVE dragging-limit CLASS
					if (limit) {
						ui.helper.addClass( helperLimitClass +" "+ helperPaneLimitClass ); // at dragging-limit
						window.defaultStatus = (limit>0 && pane.match(/north|west/)) || (limit<0 && pane.match(/south|east/)) ? lang.maxSizeWarning : lang.minSizeWarning;
					}
					else {
						ui.helper.removeClass( helperLimitClass +" "+ helperPaneLimitClass ); // not at dragging-limit
						window.defaultStatus = "";
					}
					// DYNAMICALLY RESIZE PANES IF OPTION ENABLED
					// won't trigger unless resizer has actually moved!
					if (live && Math.abs(ui.position[side] - lastPos) >= o.liveResizingTolerance) {
						lastPos = ui.position[side];
						resizePanes(e, ui, pane)
					}
				}

			,	stop: function (e, ui) {
					$('body').enableSelection(); // RE-ENABLE TEXT SELECTION
					window.defaultStatus = ""; // clear 'resizing limit' message from statusbar
					$R.removeClass( resizerClass +" "+ resizerPaneClass ); // remove drag classes from Resizer
					s.isResizing = false;
					resizePanes(e, ui, pane, true, masks); // true = resizingDone
				}

			});
		});

		/**
		* resizePanes
		*
		* Sub-routine called from stop() - and drag() if livePaneResizing
		*
		* @param {!Object}		evt
		* @param {!Object}		ui
		* @param {string}		pane
		* @param {boolean=}		[resizingDone=false]
		*/
		var resizePanes = function (evt, ui, pane, resizingDone, masks) {
			var	dragPos	= ui.position
			,	c		= _c[pane]
			,	o		= options[pane]
			,	s		= state[pane]
			,	resizerPos
			;
			switch (pane) {
				case "north":	resizerPos = dragPos.top; break;
				case "west":	resizerPos = dragPos.left; break;
				case "south":	resizerPos = sC.offsetHeight - dragPos.top  - o.spacing_open; break;
				case "east":	resizerPos = sC.offsetWidth  - dragPos.left - o.spacing_open; break;
			};
			// remove container margin from resizer position to get the pane size
			var newSize = resizerPos - sC["inset"+ c.side];

			// Disable OR Resize Mask(s) created in drag.start
			if (!resizingDone) {
				// ensure we meet liveResizingTolerance criteria
				if (Math.abs(newSize - s.size) < o.liveResizingTolerance)
					return; // SKIP resize this time
				// resize the pane
				manualSizePane(pane, newSize, false, true); // true = noAnimation
				sizeMasks(); // resize all visible masks
			}
			else { // resizingDone
				// ondrag_end callback
				if (false !== _execCallback(pane, o.ondrag_end || o.ondrag))
					manualSizePane(pane, newSize, false, true); // true = noAnimation
				hideMasks(); // hide all masks, which include panes with 'content/iframe-masks'
				if (s.isSliding && masks) // RE-SHOW only 'object-masks' so objects won't show through sliding pane
					showMasks( masks, true ); // true = onlyForObjects
			}
		};
	}

	/**
	 *	sizeMask
	 *
	 *	Needed to overlay a DIV over an IFRAME-pane because mask CANNOT be *inside* the pane
	 *	Called when mask created, and during livePaneResizing
	 */
,	sizeMask = function () {
		var $M		= $(this)
		,	pane	= $M.data("layoutMask") // eg: "west"
		,	s		= state[pane]
		;
		// only masks over an IFRAME-pane need manual resizing
		if (s.tagName == "IFRAME" && s.isVisible) // no need to mask closed/hidden panes
			$M.css({
				top:	s.offsetTop
			,	left:	s.offsetLeft
			,	width:	s.outerWidth
			,	height:	s.outerHeight
			});
		/* ALT Method...
		var $P = $Ps[pane];
		$M.css( $P.position() ).css({ width: $P[0].offsetWidth, height: $P[0].offsetHeight });
		*/
	}
,	sizeMasks = function () {
		$Ms.each( sizeMask ); // resize all 'visible' masks
	}

,	showMasks = function (panes, onlyForObjects) {
		var a	= panes ? panes.split(",") : $.layout.config.allPanes
		,	z	= options.zIndexes
		,	o, s;
		$.each(a, function(i,p){
			s = state[p];
			o = options[p];
			if (s.isVisible && ( (!onlyForObjects && o.maskContents) || o.maskObjects )) {
				getMasks(p).each(function(){
					sizeMask.apply(this);
					this.style.zIndex = s.isSliding ? z.pane_sliding+1 : z.pane_normal+1
					this.style.display = "block";
				});
			}
		});
	}

,	hideMasks = function () {
		// ensure no pane is resizing - could be a timing issue
		var skip;
		$.each( $.layout.config.borderPanes, function(i,p){
			if (state[p].isResizing) {
				skip = true;
				return false; // BREAK
			}
		});
		if (!skip)
			$Ms.hide(); // hide ALL masks
	}

,	getMasks = function (pane) {
		var $Masks	= $([])
		,	$M, i = 0, c = $Ms.length
		;
		for (; i<c; i++) {
			$M = $Ms.eq(i);
			if ($M.data("layoutMask") === pane)
				$Masks = $Masks.add( $M );
		}
		if ($Masks.length)
			return $Masks;
		else
			return createMasks(pane);
	}

	/**
	 *	createMasks
	 *
	 *	Generates both DIV (ALWAYS used) and IFRAME (optional) elements as masks
	 *	An IFRAME mask is created *under* the DIV when maskObjects=true, because a DIV cannot mask an applet
	 */
,	createMasks = function (pane) {
		var
			$P		= $Ps[pane]
		,	s		= state[pane]
		,	o		= options[pane]
		,	z		= options.zIndexes
		//,	objMask	= o.maskObjects && s.tagName != "IFRAME" // check for option
		,	$Masks	= $([])
		,	isIframe, el, $M, css, i
		;
		if (!o.maskContents && !o.maskObjects) return $Masks;
		// if o.maskObjects=true, then loop TWICE to create BOTH kinds of mask, else only create a DIV
		for (i=0; i < (o.maskObjects ? 2 : 1); i++) {
			isIframe = o.maskObjects && i==0;
			el = document.createElement( isIframe ? "iframe" : "div" );
			$M = $(el).data("layoutMask", pane); // add data to relate mask to pane
			el.className = "ui-layout-mask ui-layout-mask-"+ pane; // for user styling
			css = el.style;
			// styles common to both DIVs and IFRAMES
			css.display		= "block";
			css.position	= "absolute";
			if (isIframe) { // IFRAME-only props
				el.frameborder = 0;
				el.src		= "about:blank";
				css.opacity	= 0;
				css.filter	= "Alpha(Opacity='0')";
				css.border	= 0;
			}
			// if pane is an IFRAME, then must mask the pane itself
			if (s.tagName == "IFRAME") {
				// NOTE sizing done by a subroutine so can be called during live-resizing
				css.zIndex	= z.pane_normal+1; // 1-higher than pane
				$N.append( el ); // append to LAYOUT CONTAINER
			}
			// otherwise put masks *inside the pane* to mask its contents
			else {
				$M.addClass("ui-layout-mask-inside-pane");
				css.zIndex	= o.maskZindex || z.content_mask; // usually 1, but customizable
				css.top		= 0;
				css.left	= 0;
				css.width	= "100%";
				css.height	= "100%";
				$P.append( el ); // append INSIDE pane element
			}
			// add to return object
			$Masks = $Masks.add( el );
			// add Mask to cached array so can be resized & reused
			$Ms = $Ms.add( el );
		}
		return $Masks;
	}


	/**
	* Destroy this layout and reset all elements
	*
	* @param {boolean=}	[destroyChildren=false]		Destory Child-Layouts first?
	*/
,	destroy = function (destroyChildren) {
		// UNBIND layout events and remove global object
		$(window).unbind("."+ sID);		// resize & unload
		$(document).unbind("."+ sID);	// keyDown (hotkeys)

		// reset layout-container
		$N	.clearQueue()
			.removeData("layout")
			.removeData("layoutContainer")
			.removeClass(options.containerClass)
		;

		// remove all mask elements that have been created
		$Ms.remove();

		// loop all panes to remove layout classes, attributes and bindings
		$.each(_c.allPanes, function (i, pane) {
			removePane( pane, false, true, destroyChildren ); // true = skipResize
		});

		// do NOT reset container CSS if is a 'pane' (or 'content') in an outer-layout - ie, THIS layout is 'nested'
		var css = "layoutCSS";
		if ($N.data(css) && !$N.data("layoutRole")) // RESET CSS
			$N.css( $N.data(css) ).removeData(css);

		// for full-page layouts, also reset the <HTML> CSS
		if (sC.tagName === "BODY" && ($N = $("html")).data(css)) // RESET <HTML> CSS
			$N.css( $N.data(css) ).removeData(css);

		// trigger plugins for this layout, if there are any
		runPluginCallbacks( Instance, $.layout.onDestroy );

		// trigger state-management and onunload callback
		unload();

		// clear the Instance of everything except for container & options (so could recreate)
		// RE-CREATE: myLayout = myLayout.container.layout( myLayout.options );
		for (n in Instance)
			if (!n.match(/^(container|options)$/)) delete Instance[ n ];
		// add a 'destroyed' flag to make it easy to check
		Instance.destroyed = true;
	}

	/**
	* Remove a pane from the layout - subroutine of destroy()
	*
	* @see  destroy()
	* @param {string}	pane				The pane to process
	* @param {boolean=}	[remove=false]		Remove the DOM element?
	* @param {boolean=}	[skipResize=false]	Skip calling resizeAll()?
	*/
,	removePane = function (pane, remove, skipResize, destroyChild) {
		if (!isInitialized()) return;
		var	$P	= $Ps[pane]
		,	$C	= $Cs[pane]
		,	$R	= $Rs[pane]
		,	$T	= $Ts[pane]
		;
		// NOTE: elements can still exist even after remove()
		//		so check for missing data(), which is cleared by removed()
		if ($P && $.isEmptyObject( $P.data() )) $P = false;
		if ($C && $.isEmptyObject( $C.data() )) $C = false;
		if ($R && $.isEmptyObject( $R.data() )) $R = false;
		if ($T && $.isEmptyObject( $T.data() )) $T = false;

		if ($P) $P.stop(true, true);

		//	check for a child layout
		var	o	= options[pane]
		,	s	= state[pane]
		,	d	= "layout"
		,	css	= "layoutCSS"
		,	Child	= children[pane] || ($P ? $P.data(d) : 0) || ($C ? $C.data(d) : 0) || null
		,	destroy	= destroyChild !== undefined ? destroyChild : o.destroyChildLayout
		;

		// FIRST destroy the child-layout(s)
		if (destroy && Child && !Child.destroyed) {
			Child.destroy(true);	// tell child-layout to destroy ALL its child-layouts too
			if (Child.destroyed)	// destroy was successful
				Child = null;		// clear pointer for logic below 
		}

		if ($P && remove && !Child)
			$P.remove();
		else if ($P) {
			//	create list of ALL pane-classes that need to be removed
			var	root	= o.paneClass // default="ui-layout-pane"
			,	pRoot	= root +"-"+ pane // eg: "ui-layout-pane-west"
			,	_open	= "-open"
			,	_sliding= "-sliding"
			,	_closed	= "-closed"
			,	classes	= [	root, root+_open, root+_closed, root+_sliding,		// generic classes
							pRoot, pRoot+_open, pRoot+_closed, pRoot+_sliding ]	// pane-specific classes
			;
			$.merge(classes, getHoverClasses($P, true)); // ADD hover-classes
			// remove all Layout classes from pane-element
			$P	.removeClass( classes.join(" ") ) // remove ALL pane-classes
				.removeData("layoutParent")
				.removeData("layoutRole")
				.removeData("layoutEdge")
				.removeData("autoHidden")	// in case set
				.unbind("."+ sID) // remove ALL Layout events
				// TODO: remove these extra unbind commands when jQuery is fixed
				//.unbind("mouseenter"+ sID)
				//.unbind("mouseleave"+ sID)
			;
			// do NOT reset CSS if this pane/content is STILL the container of a nested layout!
			// the nested layout will reset its 'container' CSS when/if it is destroyed
			if ($C && $C.data(d)) {
				// a content-div may not have a specific width, so give it one to contain the Layout
				$C.width( $C.width() );
				Child.resizeAll(); // now resize the Layout
			}
			else if ($C)
				$C.css( $C.data(css) ).removeData(css).removeData("layoutRole");
			// remove pane AFTER content in case there was a nested layout
			if (!$P.data(d))
				$P.css( $P.data(css) ).removeData(css);
		}

		// REMOVE pane resizer and toggler elements
		if ($T) $T.remove();
		if ($R) $R.remove();

		// CLEAR all pointers and state data
		Instance[pane] = $Ps[pane] = $Cs[pane] = $Rs[pane] = $Ts[pane] = children[pane] = false;
		s = { removed: true };

		if (!skipResize)
			resizeAll();
	}


/*
 * ###########################
 *	   ACTION METHODS
 * ###########################
 */

	/**
	* Completely 'hides' a pane, including its spacing - as if it does not exist
	* The pane is not actually 'removed' from the source, so can use 'show' to un-hide it
	*
	* @param {string}	pane		The pane being hidden, ie: north, south, east, or west
	* @param {boolean=}	[noAnimation=false]	
	*/
,	hide = function (pane, noAnimation) {
		if (!isInitialized()) return;
		var
			o	= options[pane]
		,	s	= state[pane]
		,	$P	= $Ps[pane]
		,	$R	= $Rs[pane]
		;
		if (!$P || s.isHidden) return; // pane does not exist OR is already hidden

		// onhide_start callback - will CANCEL hide if returns false
		if (state.initialized && false === _execCallback(pane, o.onhide_start)) return;

		s.isSliding = false; // just in case

		// now hide the elements
		if ($R) $R.hide(); // hide resizer-bar
		if (!state.initialized || s.isClosed) {
			s.isClosed = true; // to trigger open-animation on show()
			s.isHidden  = true;
			s.isVisible = false;
			$P.hide(); // no animation when loading page
			sizeMidPanes(_c[pane].dir === "horz" ? "" : "center");
			if (state.initialized || o.triggerEventsOnLoad)
				_execCallback(pane, o.onhide_end || o.onhide);
		}
		else {
			s.isHiding = true; // used by onclose
			close(pane, false, noAnimation); // adjust all panes to fit
		}
	}

	/**
	* Show a hidden pane - show as 'closed' by default unless openPane = true
	*
	* @param {string}	pane		The pane being opened, ie: north, south, east, or west
	* @param {boolean=}	[openPane=false]
	* @param {boolean=}	[noAnimation=false]
	* @param {boolean=}	[noAlert=false]
	*/
,	show = function (pane, openPane, noAnimation, noAlert) {
		if (!isInitialized()) return;
		var
			o	= options[pane]
		,	s	= state[pane]
		,	$P	= $Ps[pane]
		,	$R	= $Rs[pane]
		;
		if (!$P || !s.isHidden) return; // pane does not exist OR is not hidden

		// onshow_start callback - will CANCEL show if returns false
		if (false === _execCallback(pane, o.onshow_start)) return;

		s.isSliding = false; // just in case
		s.isShowing = true; // used by onopen/onclose
		//s.isHidden  = false; - will be set by open/close - if not cancelled

		// now show the elements
		//if ($R) $R.show(); - will be shown by open/close
		if (openPane === false)
			close(pane, true); // true = force
		else
			open(pane, false, noAnimation, noAlert); // adjust all panes to fit
	}


	/**
	* Toggles a pane open/closed by calling either open or close
	*
	* @param {string}	pane   The pane being toggled, ie: north, south, east, or west
	* @param {boolean=}	[slide=false]
	*/
,	toggle = function (pane, slide) {
		if (!isInitialized()) return;
		if (!isStr(pane)) {
			pane.stopImmediatePropagation(); // pane = event
			pane = $(this).data("layoutEdge"); // bound to $R.dblclick
		}
		var s = state[pane];
		if (s.isHidden)
			show(pane); // will call 'open' after unhiding it
		else if (s.isClosed)
			open(pane, !!slide);
		else
			close(pane);
	}


	/**
	* Utility method used during init or other auto-processes
	*
	* @param {string}	pane   The pane being closed
	* @param {boolean=}	[setHandles=false]
	*/
,	_closePane = function (pane, setHandles) {
		var
			$P	= $Ps[pane]
		,	s	= state[pane]
		;
		$P.hide();
		s.isClosed = true;
		s.isVisible = false;
		// UNUSED: if (setHandles) setAsClosed(pane, true); // true = force
	}

	/**
	* Close the specified pane (animation optional), and resize all other panes as needed
	*
	* @param {string}	pane		The pane being closed, ie: north, south, east, or west
	* @param {boolean=}	[force=false]
	* @param {boolean=}	[noAnimation=false]
	* @param {boolean=}	[skipCallback=false]
	*/
,	close = function (pane, force, noAnimation, skipCallback) {
		if (!state.initialized && $Ps[pane]) {
			_closePane(pane); // INIT pane as closed
			return;
		}
		if (!isInitialized()) return;

		var
			$P	= $Ps[pane]
		,	$R	= $Rs[pane]
		,	$T	= $Ts[pane]
		,	o	= options[pane]
		,	s	= state[pane]
		,	c	= _c[pane]
		,	doFX, isShowing, isHiding, wasSliding;

		// QUEUE in case another action/animation is in progress
		$N.queue(function( queueNext ){
	
			if ( !$P
			||	(!o.closable && !s.isShowing && !s.isHiding)	// invalid request // (!o.resizable && !o.closable) ???
			||	(!force && s.isClosed && !s.isShowing)			// already closed
			) return queueNext();

			// onclose_start callback - will CANCEL hide if returns false
			// SKIP if just 'showing' a hidden pane as 'closed'
			var abort = !s.isShowing && false === _execCallback(pane, o.onclose_start);

			// transfer logic vars to temp vars
			isShowing	= s.isShowing;
			isHiding	= s.isHiding;
			wasSliding	= s.isSliding;
			// now clear the logic vars
			delete s.isShowing;
			delete s.isHiding;

			if (abort) return queueNext();

			doFX		= !noAnimation && !s.isClosed && (o.fxName_close != "none");
			s.isMoving	= true;
			s.isClosed	= true;
			s.isVisible	= false;
			// update isHidden BEFORE sizing panes
			if (isHiding) s.isHidden = true;
			else if (isShowing) s.isHidden = false;

			if (s.isSliding) // pane is being closed, so UNBIND trigger events
				bindStopSlidingEvents(pane, false); // will set isSliding=false
			else // resize panes adjacent to this one
				sizeMidPanes(_c[pane].dir === "horz" ? "" : "center", false); // false = NOT skipCallback

			// if this pane has a resizer bar, move it NOW - before animation
			setAsClosed(pane);

			// CLOSE THE PANE
			if (doFX) { // animate the close
				// mask panes with objects
				var masks = "center"+ (c.dir=="horz" ? ",west,east" : "");
				showMasks( masks, true );	// true = ONLY mask panes with maskObjects=true
				lockPaneForFX(pane, true);	// need to set left/top so animation will work
				$P.hide( o.fxName_close, o.fxSettings_close, o.fxSpeed_close, function () {
					lockPaneForFX(pane, false); // undo
					if (s.isClosed) close_2();
					queueNext();
				});
			}
			else { // hide the pane without animation
				$P.hide();
				close_2();
				queueNext();
			};
		});

		// SUBROUTINE
		function close_2 () {
			s.isMoving	= false;
			bindStartSlidingEvent(pane, true); // will enable if o.slidable = true

			// if opposite-pane was autoClosed, see if it can be autoOpened now
			var altPane = _c.oppositeEdge[pane];
			if (state[ altPane ].noRoom) {
				setSizeLimits( altPane );
				makePaneFit( altPane );
			}

			// hide any masks shown while closing
			hideMasks();

			if (!skipCallback && (state.initialized || o.triggerEventsOnLoad)) {
				// onclose callback - UNLESS just 'showing' a hidden pane as 'closed'
				if (!isShowing) _execCallback(pane, o.onclose_end || o.onclose);
				// onhide OR onshow callback
				if (isShowing)	_execCallback(pane, o.onshow_end || o.onshow);
				if (isHiding)	_execCallback(pane, o.onhide_end || o.onhide);
			}
		}
	}

	/**
	* @param {string}	pane	The pane just closed, ie: north, south, east, or west
	*/
,	setAsClosed = function (pane) {
		var
			$P		= $Ps[pane]
		,	$R		= $Rs[pane]
		,	$T		= $Ts[pane]
		,	o		= options[pane]
		,	s		= state[pane]
		,	side	= _c[pane].side.toLowerCase()
		,	inset	= "inset"+ _c[pane].side
		,	rClass	= o.resizerClass
		,	tClass	= o.togglerClass
		,	_pane	= "-"+ pane // used for classNames
		,	_open	= "-open"
		,	_sliding= "-sliding"
		,	_closed	= "-closed"
		;
		$R
			.css(side, sC[inset]) // move the resizer
			.removeClass( rClass+_open +" "+ rClass+_pane+_open )
			.removeClass( rClass+_sliding +" "+ rClass+_pane+_sliding )
			.addClass( rClass+_closed +" "+ rClass+_pane+_closed )
			.unbind("dblclick."+ sID)
		;
		// DISABLE 'resizing' when closed - do this BEFORE bindStartSlidingEvent?
		if (o.resizable && $.layout.plugins.draggable)
			$R
				.draggable("disable")
				.removeClass("ui-state-disabled") // do NOT apply disabled styling - not suitable here
				.css("cursor", "default")
				.attr("title","")
			;

		// if pane has a toggler button, adjust that too
		if ($T) {
			$T
				.removeClass( tClass+_open +" "+ tClass+_pane+_open )
				.addClass( tClass+_closed +" "+ tClass+_pane+_closed )
				.attr("title", o.togglerTip_closed) // may be blank
			;
			// toggler-content - if exists
			$T.children(".content-open").hide();
			$T.children(".content-closed").css("display","block");
		}

		// sync any 'pin buttons'
		syncPinBtns(pane, false);

		if (state.initialized) {
			// resize 'length' and position togglers for adjacent panes
			sizeHandles();
		}
	}

	/**
	* Open the specified pane (animation optional), and resize all other panes as needed
	*
	* @param {string}	pane		The pane being opened, ie: north, south, east, or west
	* @param {boolean=}	[slide=false]
	* @param {boolean=}	[noAnimation=false]
	* @param {boolean=}	[noAlert=false]
	*/
,	open = function (pane, slide, noAnimation, noAlert) {
		if (!isInitialized()) return;
		var 
			$P	= $Ps[pane]
		,	$R	= $Rs[pane]
		,	$T	= $Ts[pane]
		,	o	= options[pane]
		,	s	= state[pane]
		,	c	= _c[pane]
		,	doFX, isShowing;

		// QUEUE in case another action/animation is in progress
		$N.queue(function( queueNext ){

			if ( !$P
			||	(!o.resizable && !o.closable && !s.isShowing)	// invalid request
			||	(s.isVisible && !s.isSliding)					// already open
			) return queueNext();

			// pane can ALSO be unhidden by just calling show(), so handle this scenario
			if (s.isHidden && !s.isShowing) {
				show(pane, true);
				return;
			}

			if (o.autoResize && s.size != o.size) // resize pane to original size set in options
				sizePane(pane, o.size, true, true, true); // true=skipCallback/forceResize/noAnimation
			else
				setSizeLimits(pane, slide);

			//setSizeLimits(pane, slide); // update pane-state
			// onopen_start callback - will CANCEL hide if returns false
			if (false === _execCallback(pane, o.onopen_start)) return;

			// make sure there is enough space available to open the pane
			setSizeLimits(pane, slide); // update pane-state
			if (s.minSize > s.maxSize) { // INSUFFICIENT ROOM FOR PANE TO OPEN!
				syncPinBtns(pane, false); // make sure pin-buttons are reset
				if (!noAlert && o.noRoomToOpenTip)
					alert(o.noRoomToOpenTip);
				return queueNext(); // ABORT
			}

			if (slide) // START Sliding - will set isSliding=true
				bindStopSlidingEvents(pane, true); // BIND trigger events to close sliding-pane
			else if (s.isSliding) // PIN PANE (stop sliding) - open pane 'normally' instead
				bindStopSlidingEvents(pane, false); // UNBIND trigger events - will set isSliding=false
			else if (o.slidable)
				bindStartSlidingEvent(pane, false); // UNBIND trigger events

			s.noRoom = false; // will be reset by makePaneFit if 'noRoom'
			makePaneFit(pane);

			// transfer logic var to temp var
			isShowing = s.isShowing;
			// now clear the logic var
			delete s.isShowing;

			doFX		= !noAnimation && s.isClosed && (o.fxName_open != "none");
			s.isMoving	= true;
			s.isVisible	= true;
			s.isClosed	= false;
			// update isHidden BEFORE sizing panes - WHY??? Old?
			if (isShowing) s.isHidden = false;
	
			if (doFX) { // ANIMATE
				// mask panes with objects
				var masks = "center"+ (c.dir=="horz" ? ",west,east" : "");
				if (s.isSliding) masks += ","+ _c.oppositeEdge[pane];
				showMasks( masks, true );	// true = ONLY mask panes with maskObjects=true
				lockPaneForFX(pane, true);	// need to set left/top so animation will work
				$P.show( o.fxName_open, o.fxSettings_open, o.fxSpeed_open, function() {
					lockPaneForFX(pane, false); // undo
					if (s.isVisible) open_2(); // continue
					queueNext();
				});
			}
			else {// no animation
				$P.show();	// just show pane and...
				open_2();	// continue
				queueNext();
			};
		});

		// SUBROUTINE
		function open_2 () {
			s.isMoving	= false;

			// cure iframe display issues
			_fixIframe(pane);

			// NOTE: if isSliding, then other panes are NOT 'resized'
			if (!s.isSliding) { // resize all panes adjacent to this one
				hideMasks(); // remove any masks shown while opening
				sizeMidPanes(_c[pane].dir=="vert" ? "center" : "", false); // false = NOT skipCallback
			}

			// set classes, position handles and execute callbacks...
			setAsOpen(pane);
		};
	
	}

	/**
	* @param {string}	pane		The pane just opened, ie: north, south, east, or west
	* @param {boolean=}	[skipCallback=false]
	*/
,	setAsOpen = function (pane, skipCallback) {
		var 
			$P		= $Ps[pane]
		,	$R		= $Rs[pane]
		,	$T		= $Ts[pane]
		,	o		= options[pane]
		,	s		= state[pane]
		,	side	= _c[pane].side.toLowerCase()
		,	inset	= "inset"+ _c[pane].side
		,	rClass	= o.resizerClass
		,	tClass	= o.togglerClass
		,	_pane	= "-"+ pane // used for classNames
		,	_open	= "-open"
		,	_closed	= "-closed"
		,	_sliding= "-sliding"
		;
		$R
			.css(side, sC[inset] + getPaneSize(pane)) // move the resizer
			.removeClass( rClass+_closed +" "+ rClass+_pane+_closed )
			.addClass( rClass+_open +" "+ rClass+_pane+_open )
		;
		if (s.isSliding)
			$R.addClass( rClass+_sliding +" "+ rClass+_pane+_sliding )
		else // in case 'was sliding'
			$R.removeClass( rClass+_sliding +" "+ rClass+_pane+_sliding )

		if (o.resizerDblClickToggle)
			$R.bind("dblclick", toggle );
		removeHover( 0, $R ); // remove hover classes
		if (o.resizable && $.layout.plugins.draggable)
			$R	.draggable("enable")
				.css("cursor", o.resizerCursor)
				.attr("title", o.resizerTip);
		else if (!s.isSliding)
			$R.css("cursor", "default"); // n-resize, s-resize, etc

		// if pane also has a toggler button, adjust that too
		if ($T) {
			$T	.removeClass( tClass+_closed +" "+ tClass+_pane+_closed )
				.addClass( tClass+_open +" "+ tClass+_pane+_open )
				.attr("title", o.togglerTip_open); // may be blank
			removeHover( 0, $T ); // remove hover classes
			// toggler-content - if exists
			$T.children(".content-closed").hide();
			$T.children(".content-open").css("display","block");
		}

		// sync any 'pin buttons'
		syncPinBtns(pane, !s.isSliding);

		// update pane-state dimensions - BEFORE resizing content
		$.extend(s, elDims($P));

		if (state.initialized) {
			// resize resizer & toggler sizes for all panes
			sizeHandles();
			// resize content every time pane opens - to be sure
			sizeContent(pane, true); // true = remeasure headers/footers, even if 'pane.isMoving'
		}

		if (!skipCallback && (state.initialized || o.triggerEventsOnLoad) && $P.is(":visible")) {
			// onopen callback
			_execCallback(pane, o.onopen_end || o.onopen);
			// onshow callback - TODO: should this be here?
			if (s.isShowing) _execCallback(pane, o.onshow_end || o.onshow);
			// ALSO call onresize because layout-size *may* have changed while pane was closed
			if (state.initialized) {
				_execCallback(pane, o.onresize_end || o.onresize);
				resizeChildLayout(pane);
			}
		}
	}


	/**
	* slideOpen / slideClose / slideToggle
	*
	* Pass-though methods for sliding
	*/
,	slideOpen = function (evt_or_pane) {
		if (!isInitialized()) return;
		var
			evt		= isStr(evt_or_pane) ? null : evt_or_pane
		,	pane	= evt ? $(this).data("layoutEdge") : evt_or_pane
		,	s		= state[pane]
		,	delay	= options[pane].slideDelay_open
		;
		// prevent event from triggering on NEW resizer binding created below
		if (evt) evt.stopImmediatePropagation();

		if (s.isClosed && evt && evt.type === "mouseenter" && delay > 0)
			// trigger = mouseenter - use a delay
			timer.set(pane+"_openSlider", open_NOW, delay);
		else
			open_NOW(); // will unbind events if is already open

		/**
		* SUBROUTINE for timed open
		*/
		function open_NOW () {
			if (!s.isClosed) // skip if no longer closed!
				bindStopSlidingEvents(pane, true); // BIND trigger events to close sliding-pane
			else if (!s.isMoving)
				open(pane, true); // true = slide - open() will handle binding
		};
	}

,	slideClose = function (evt_or_pane) {
		if (!isInitialized()) return;
		var
			evt		= isStr(evt_or_pane) ? null : evt_or_pane
		,	pane	= evt ? $(this).data("layoutEdge") : evt_or_pane
		,	o		= options[pane]
		,	s		= state[pane]
		,	delay	= s.isMoving ? 1000 : 300 // MINIMUM delay - option may override
		;
		if (s.isClosed || s.isResizing)
			return; // skip if already closed OR in process of resizing
		else if (o.slideTrigger_close === "click")
			close_NOW(); // close immediately onClick
		else if (o.preventQuickSlideClose && s.isMoving)
			return; // handle Chrome quick-close on slide-open
		else if (o.preventPrematureSlideClose && evt && $.layout.isMouseOverElem(evt, $Ps[pane]))
			return; // handle incorrect mouseleave trigger, like when over a SELECT-list in IE
		else if (evt) // trigger = mouseleave - use a delay
			// 1 sec delay if 'opening', else .3 sec
			timer.set(pane+"_closeSlider", close_NOW, max(o.slideDelay_close, delay));
		else // called programically
			close_NOW();

		/**
		* SUBROUTINE for timed close
		*/
		function close_NOW () {
			if (s.isClosed) // skip 'close' if already closed!
				bindStopSlidingEvents(pane, false); // UNBIND trigger events - TODO: is this needed here?
			else if (!s.isMoving)
				close(pane); // close will handle unbinding
		};
	}

	/**
	* @param {string}	pane		The pane being opened, ie: north, south, east, or west
	*/
,	slideToggle = function (pane) { toggle(pane, true); }


	/**
	* Must set left/top on East/South panes so animation will work properly
	*
	* @param {string}	pane	The pane to lock, 'east' or 'south' - any other is ignored!
	* @param {boolean}	doLock  true = set left/top, false = remove
	*/
,	lockPaneForFX = function (pane, doLock) {
		var $P	= $Ps[pane]
		,	s	= state[pane]
		,	o	= options[pane]
		,	z	= options.zIndexes
		;
		if (doLock) {
			$P.css({ zIndex: z.pane_animate }); // overlay all elements during animation
			if (pane=="south")
				$P.css({ top: sC.insetTop + sC.innerHeight - $P.outerHeight() });
			else if (pane=="east")
				$P.css({ left: sC.insetLeft + sC.innerWidth - $P.outerWidth() });
		}
		else { // animation DONE - RESET CSS
			// TODO: see if this can be deleted. It causes a quick-close when sliding in Chrome
			$P.css({ zIndex: (s.isSliding ? z.pane_sliding : z.pane_normal) });
			if (pane=="south")
				$P.css({ top: "auto" });
			else if (pane=="east")
				$P.css({ left: "auto" });
			// fix anti-aliasing in IE - only needed for animations that change opacity
			if (browser.msie && o.fxOpacityFix && o.fxName_open != "slide" && $P.css("filter") && $P.css("opacity") == 1)
				$P[0].style.removeAttribute('filter');
		}
	}


	/**
	* Toggle sliding functionality of a specific pane on/off by adding removing 'slide open' trigger
	*
	* @see  open(), close()
	* @param {string}	pane	The pane to enable/disable, 'north', 'south', etc.
	* @param {boolean}	enable	Enable or Disable sliding?
	*/
,	bindStartSlidingEvent = function (pane, enable) {
		var o		= options[pane]
		,	$P		= $Ps[pane]
		,	$R		= $Rs[pane]
		,	trigger	= o.slideTrigger_open.toLowerCase()
		;
		if (!$R || (enable && !o.slidable)) return;

		// make sure we have a valid event
		if (trigger.match(/mouseover/))
			trigger = o.slideTrigger_open = "mouseenter";
		else if (!trigger.match(/click|dblclick|mouseenter/)) 
			trigger = o.slideTrigger_open = "click";

		$R
			// add or remove trigger event
			[enable ? "bind" : "unbind"](trigger +'.'+ sID, slideOpen)
			// set the appropriate cursor & title/tip
			.css("cursor", enable ? o.sliderCursor : "default")
			.attr("title", enable ? o.sliderTip : "")
		;
	}

	/**
	* Add or remove 'mouseleave' events to 'slide close' when pane is 'sliding' open or closed
	* Also increases zIndex when pane is sliding open
	* See bindStartSlidingEvent for code to control 'slide open'
	*
	* @see  slideOpen(), slideClose()
	* @param {string}	pane	The pane to process, 'north', 'south', etc.
	* @param {boolean}	enable	Enable or Disable events?
	*/
,	bindStopSlidingEvents = function (pane, enable) {
		var	o		= options[pane]
		,	s		= state[pane]
		,	c		= _c[pane]
		,	z		= options.zIndexes
		,	trigger	= o.slideTrigger_close.toLowerCase()
		,	action	= (enable ? "bind" : "unbind")
		,	$P		= $Ps[pane]
		,	$R		= $Rs[pane]
		;
		s.isSliding = enable; // logic
		timer.clear(pane+"_closeSlider"); // just in case

		// remove 'slideOpen' trigger event from resizer
		// ALSO will raise the zIndex of the pane & resizer
		if (enable) bindStartSlidingEvent(pane, false);

		// RE/SET zIndex - increases when pane is sliding-open, resets to normal when not
		$P.css("zIndex", enable ? z.pane_sliding : z.pane_normal);
		$R.css("zIndex", enable ? z.pane_sliding+2 : z.resizer_normal); // NOTE: mask = pane_sliding+1

		// make sure we have a valid event
		if (!trigger.match(/click|mouseleave/))
			trigger = o.slideTrigger_close = "mouseleave"; // also catches 'mouseout'

		// add/remove slide triggers
		$R[action](trigger, slideClose); // base event on resize
		// need extra events for mouseleave
		if (trigger === "mouseleave") {
			// also close on pane.mouseleave
			$P[action]("mouseleave."+ sID, slideClose);
			// cancel timer when mouse moves between 'pane' and 'resizer'
			$R[action]("mouseenter."+ sID, cancelMouseOut);
			$P[action]("mouseenter."+ sID, cancelMouseOut);
		}

		if (!enable)
			timer.clear(pane+"_closeSlider");
		else if (trigger === "click" && !o.resizable) {
			// IF pane is not resizable (which already has a cursor and tip) 
			// then set the a cursor & title/tip on resizer when sliding
			$R.css("cursor", enable ? o.sliderCursor : "default");
			$R.attr("title", enable ? o.togglerTip_open : ""); // use Toggler-tip, eg: "Close Pane"
		}

		// SUBROUTINE for mouseleave timer clearing
		function cancelMouseOut (evt) {
			timer.clear(pane+"_closeSlider");
			evt.stopPropagation();
		}
	}


	/**
	* Hides/closes a pane if there is insufficient room - reverses this when there is room again
	* MUST have already called setSizeLimits() before calling this method
	*
	* @param {string}	pane					The pane being resized
	* @param {boolean=}	[isOpening=false]		Called from onOpen?
	* @param {boolean=}	[skipCallback=false]	Should the onresize callback be run?
	* @param {boolean=}	[force=false]
	*/
,	makePaneFit = function (pane, isOpening, skipCallback, force) {
		var
			o	= options[pane]
		,	s	= state[pane]
		,	c	= _c[pane]
		,	$P	= $Ps[pane]
		,	$R	= $Rs[pane]
		,	isSidePane 	= c.dir==="vert"
		,	hasRoom		= false
		;
		// special handling for center & east/west panes
		if (pane === "center" || (isSidePane && s.noVerticalRoom)) {
			// see if there is enough room to display the pane
			// ERROR: hasRoom = s.minHeight <= s.maxHeight && (isSidePane || s.minWidth <= s.maxWidth);
			hasRoom = (s.maxHeight >= 0);
			if (hasRoom && s.noRoom) { // previously hidden due to noRoom, so show now
				$P.show();
				if ($R) $R.show();
				s.isVisible = true;
				s.noRoom = false;
				if (isSidePane) s.noVerticalRoom = false;
				_fixIframe(pane);
			}
			else if (!hasRoom && !s.noRoom) { // not currently hidden, so hide now
				$P.hide();
				if ($R) $R.hide();
				s.isVisible = false;
				s.noRoom = true;
			}
		}

		// see if there is enough room to fit the border-pane
		if (pane === "center") {
			// ignore center in this block
		}
		else if (s.minSize <= s.maxSize) { // pane CAN fit
			hasRoom = true;
			if (s.size > s.maxSize) // pane is too big - shrink it
				sizePane(pane, s.maxSize, skipCallback, force, true); // true = noAnimation
			else if (s.size < s.minSize) // pane is too small - enlarge it
				sizePane(pane, s.minSize, skipCallback, force, true);
			else if ($R && $P.is(":visible")) {
				// make sure resizer-bar is positioned correctly
				// handles situation where nested layout was 'hidden' when initialized
				var
					side = c.side.toLowerCase()
				,	pos  = s.size + sC["inset"+ c.side]
				;
				if ($.layout.cssNum($R, side) != pos) $R.css( side, pos );
			}

			// if was previously hidden due to noRoom, then RESET because NOW there is room
			if (s.noRoom) {
				// s.noRoom state will be set by open or show
				if (s.wasOpen && o.closable) {
					if (o.autoReopen)
						open(pane, false, true, true); // true = noAnimation, true = noAlert
					else // leave the pane closed, so just update state
						s.noRoom = false;
				}
				else
					show(pane, s.wasOpen, true, true); // true = noAnimation, true = noAlert
			}
		}
		else { // !hasRoom - pane CANNOT fit
			if (!s.noRoom) { // pane not set as noRoom yet, so hide or close it now...
				s.noRoom = true; // update state
				s.wasOpen = !s.isClosed && !s.isSliding;
				if (s.isClosed){} // SKIP
				else if (o.closable) // 'close' if possible
					close(pane, true, true); // true = force, true = noAnimation
				else // 'hide' pane if cannot just be closed
					hide(pane, true); // true = noAnimation
			}
		}
	}


	/**
	* sizePane / manualSizePane
	* sizePane is called only by internal methods whenever a pane needs to be resized
	* manualSizePane is an exposed flow-through method allowing extra code when pane is 'manually resized'
	*
	* @param {string}	pane					The pane being resized
	* @param {number}	size					The *desired* new size for this pane - will be validated
	* @param {boolean=}	[skipCallback=false]	Should the onresize callback be run?
	* @param {boolean=}	[noAnimation=false]
	*/
,	manualSizePane = function (pane, size, skipCallback, noAnimation) {
		if (!isInitialized()) return;
		var
			o = options[pane]
		,	s = state[pane]
		//	if resizing callbacks have been delayed and resizing is now DONE, force resizing to complete...
		,	forceResize = o.livePaneResizing && !s.isResizing
		;
		// ANY call to manualSizePane disables autoResize - ie, percentage sizing
		o.autoResize = false;
		// flow-through...
		sizePane(pane, size, skipCallback, forceResize, noAnimation); // will animate resize if option enabled
	}

	/**
	* @param {string}	pane					The pane being resized
	* @param {number}	size					The *desired* new size for this pane - will be validated
	* @param {boolean=}	[skipCallback=false]	Should the onresize callback be run?
	* @param {boolean=}	[force=false]			Force resizing even if does not seem necessary
	* @param {boolean=}	[noAnimation=false]
	*/
,	sizePane = function (pane, size, skipCallback, force, noAnimation) {
		if (!isInitialized()) return;
		var 
			o		= options[pane]
		,	s		= state[pane]
		,	$P		= $Ps[pane]
		,	$R		= $Rs[pane]
		,	side	= _c[pane].side.toLowerCase()
		,	dimName	= _c[pane].sizeType.toLowerCase()
		,	inset	= "inset"+ _c[pane].side
		,	skipResizeWhileDragging = s.isResizing && !o.triggerEventsDuringLiveResize
		,	doFX	= noAnimation !== true && o.animatePaneSizing
		,	oldSize, newSize
		;
		// QUEUE in case another action/animation is in progress
		$N.queue(function( queueNext ){
			// calculate 'current' min/max sizes
			setSizeLimits(pane); // update pane-state
			oldSize = s.size;
			size = _parseSize(pane, size); // handle percentages & auto
			size = max(size, _parseSize(pane, o.minSize));
			size = min(size, s.maxSize);
			if (size < s.minSize) { // not enough room for pane!
				makePaneFit(pane, false, skipCallback);	// will hide or close pane
				return queueNext();
			}

			// IF newSize is same as oldSize, then nothing to do - abort
			if (!force && size === oldSize)
				return queueNext();

			// onresize_start callback CANNOT cancel resizing because this would break the layout!
			if (!skipCallback && state.initialized && s.isVisible)
				_execCallback(pane, o.onresize_start);

			// resize the pane, and make sure its visible
			newSize = cssSize(pane, size);

			if (doFX && $P.is(":visible")) { // ANIMATE
				var fx		= $.layout.effects.size[pane] || $.layout.effects.size.all
				,	easing	= o.fxSettings_size.easing || fx.easing
				,	z		= options.zIndexes
				,	props	= {};
				props[ dimName ] = newSize +'px';
				s.isMoving = true;
				// overlay all elements during animation
				$P.css({ zIndex: z.pane_animate })
				  .show().animate( props, o.fxSpeed_size, easing, function(){
					// reset zIndex after animation
					$P.css({ zIndex: (s.isSliding ? z.pane_sliding : z.pane_normal) });
					s.isMoving = false;
					sizePane_2(); // continue
					queueNext();
				});
			}
			else { // no animation
				$P.css( dimName, newSize );	// resize pane
				// if pane is visible, then 
				if ($P.is(":visible"))
					sizePane_2(); // continue
				else {
					// pane is NOT VISIBLE, so just update state data...
					// when pane is *next opened*, it will have the new size
					s.size = size;				// update state.size
					$.extend(s, elDims($P));	// update state dimensions
				}
				queueNext();
			};

		});

		// SUBROUTINE
		function sizePane_2 () {
			/*	Panes are sometimes not sized precisely in some browsers!?
			 *	This code will resize the pane up to 3 times to nudge the pane to the correct size
			 */
			var	actual	= dimName==='width' ? $P.outerWidth() : $P.outerHeight()
			,	tries	= [{
						   	pane:		pane
						,	count:		1
						,	target:		size
						,	actual:		actual
						,	correct:	(size === actual)
						,	attempt:	size
						,	cssSize:	newSize
						}]
			,	lastTry = tries[0]
			,	msg		= 'Inaccurate size after resizing the '+ pane +'-pane.'
			;
			while ( !lastTry.correct ) {
				thisTry = { pane: pane, count: lastTry.count+1, target: size };

				if (lastTry.actual > size)
					thisTry.attempt = max(0, lastTry.attempt - (lastTry.actual - size));
				else // lastTry.actual < size
					thisTry.attempt = max(0, lastTry.attempt + (size - lastTry.actual));

				thisTry.cssSize = cssSize(pane, thisTry.attempt);
				$P.css( dimName, thisTry.cssSize );

				thisTry.actual	= dimName=='width' ? $P.outerWidth() : $P.outerHeight();
				thisTry.correct	= (size === thisTry.actual);

				// if showDebugMessages, log attempts and alert the user of this *non-fatal error*
				if (options.showDebugMessages) {
					if ( tries.length === 1) {
						_log(msg, false);
						_log(lastTry, false);
					}
					_log(thisTry, false);
				}

				// after 4 tries, is as close as its gonna get!
				if (tries.length > 3) break;

				tries.push( thisTry );
				lastTry = tries[ tries.length - 1 ];
			}
			// END TESTING CODE

			// update pane-state dimensions
			s.size = size;
			$.extend(s, elDims($P));

			// reposition the resizer-bar
			if ($R && $P.is(":visible")) $R.css( side, size + sC[inset] );

			sizeContent(pane);

			if (!skipCallback && !skipResizeWhileDragging && state.initialized && s.isVisible) {
				_execCallback(pane, o.onresize_end || o.onresize);
				resizeChildLayout(pane);
			}

			// resize all the adjacent panes, and adjust their toggler buttons
			// when skipCallback passed, it means the controlling method will handle 'other panes'
			if (!skipCallback) {
				// also no callback if live-resize is in progress and NOT triggerEventsDuringLiveResize
				if (!s.isSliding) sizeMidPanes(_c[pane].dir=="horz" ? "" : "center", skipResizeWhileDragging, force);
				sizeHandles();
			}

			// if opposite-pane was autoClosed, see if it can be autoOpened now
			var altPane = _c.oppositeEdge[pane];
			if (size < oldSize && state[ altPane ].noRoom) {
				setSizeLimits( altPane );
				makePaneFit( altPane, false, skipCallback );
			}

			// DEBUG - ALERT user/developer so they know there was a sizing problem
			if (options.showDebugMessages && tries.length > 1)
				_log(msg +'\nSee the Error Console for details.', true);
		}
	}

	/**
	* @see  initPanes(), sizePane(), resizeAll(), open(), close(), hide()
	* @param {string}	panes					The pane(s) being resized, comma-delmited string
	* @param {boolean=}	[skipCallback=false]	Should the onresize callback be run?
	* @param {boolean=}	[force=false]
	*/
,	sizeMidPanes = function (panes, skipCallback, force) {
		panes = (panes ? panes : "east,west,center").split(",");

		$.each(panes, function (i, pane) {
			if (!$Ps[pane]) return; // NO PANE - skip
			var 
				o		= options[pane]
			,	s		= state[pane]
			,	$P		= $Ps[pane]
			,	$R		= $Rs[pane]
			,	isCenter= (pane=="center")
			,	hasRoom	= true
			,	CSS		= {}
			,	newCenter	= calcNewCenterPaneDims()
			;
			// update pane-state dimensions
			$.extend(s, elDims($P));

			if (pane === "center") {
				if (!force && s.isVisible && newCenter.width === s.outerWidth && newCenter.height === s.outerHeight)
					return true; // SKIP - pane already the correct size
				// set state for makePaneFit() logic
				$.extend(s, cssMinDims(pane), {
					maxWidth:	newCenter.width
				,	maxHeight:	newCenter.height
				});
				CSS = newCenter;
				// convert OUTER width/height to CSS width/height 
				CSS.width	= cssW($P, CSS.width);
				// NEW - allow pane to extend 'below' visible area rather than hide it
				CSS.height	= cssH($P, CSS.height);
				hasRoom		= CSS.width >= 0 && CSS.height >= 0; // height >= 0 = ALWAYS TRUE NOW
				// during layout init, try to shrink east/west panes to make room for center
				if (!state.initialized && o.minWidth > s.outerWidth) {
					var
						reqPx	= o.minWidth - s.outerWidth
					,	minE	= options.east.minSize || 0
					,	minW	= options.west.minSize || 0
					,	sizeE	= state.east.size
					,	sizeW	= state.west.size
					,	newE	= sizeE
					,	newW	= sizeW
					;
					if (reqPx > 0 && state.east.isVisible && sizeE > minE) {
						newE = max( sizeE-minE, sizeE-reqPx );
						reqPx -= sizeE-newE;
					}
					if (reqPx > 0 && state.west.isVisible && sizeW > minW) {
						newW = max( sizeW-minW, sizeW-reqPx );
						reqPx -= sizeW-newW;
					}
					// IF we found enough extra space, then resize the border panes as calculated
					if (reqPx === 0) {
						if (sizeE != minE)
							sizePane('east', newE, true, force, true); // true = skipCallback/noAnimation - initPanes will handle when done
						if (sizeW != minW)
							sizePane('west', newW, true, force, true);
						// now start over!
						sizeMidPanes('center', skipCallback, force);
						return; // abort this loop
					}
				}
			}
			else { // for east and west, set only the height, which is same as center height
				// set state.min/maxWidth/Height for makePaneFit() logic
				if (s.isVisible && !s.noVerticalRoom)
					$.extend(s, elDims($P), cssMinDims(pane))
				if (!force && !s.noVerticalRoom && newCenter.height === s.outerHeight)
					return true; // SKIP - pane already the correct size
				// east/west have same top, bottom & height as center
				CSS.top		= newCenter.top;
				CSS.bottom	= newCenter.bottom;
				// NEW - allow pane to extend 'below' visible area rather than hide it
				CSS.height	= cssH($P, newCenter.height);
				s.maxHeight	= CSS.height;
				hasRoom		= (s.maxHeight >= 0); // ALWAYS TRUE NOW
				if (!hasRoom) s.noVerticalRoom = true; // makePaneFit() logic
			}

			if (hasRoom) {
				// resizeAll passes skipCallback because it triggers callbacks after ALL panes are resized
				if (!skipCallback && state.initialized)
					_execCallback(pane, o.onresize_start);

				$P.css(CSS); // apply the CSS to pane
				if (s.noRoom && !s.isClosed && !s.isHidden)
					makePaneFit(pane); // will re-open/show auto-closed/hidden pane
				if (s.isVisible) {
					$.extend(s, elDims($P)); // update pane dimensions
					if (state.initialized) sizeContent(pane); // also resize the contents, if exists
				}
			}
			else if (!s.noRoom && s.isVisible) // no room for pane
				makePaneFit(pane); // will hide or close pane

			if (!s.isVisible)
				return true; // DONE - next pane

			/*
			* Extra CSS for IE6 or IE7 in Quirks-mode - add 'width' to NORTH/SOUTH panes
			* Normally these panes have only 'left' & 'right' positions so pane auto-sizes
			* ALSO required when pane is an IFRAME because will NOT default to 'full width'
			*/
			if (pane === "center") { // finished processing midPanes
				var b = $.layout.browser;
				var fix = b.isIE6 || (b.msie && !$.support.boxModel);
				if ($Ps.north && (fix || state.north.tagName=="IFRAME")) 
					$Ps.north.css("width", cssW($Ps.north, sC.innerWidth));
				if ($Ps.south && (fix || state.south.tagName=="IFRAME"))
					$Ps.south.css("width", cssW($Ps.south, sC.innerWidth));
			}

			// resizeAll passes skipCallback because it triggers callbacks after ALL panes are resized
			if (!skipCallback && state.initialized) {
				_execCallback(pane, o.onresize_end || o.onresize);
				resizeChildLayout(pane);
			}
		});
	}


	/**
	* @see  window.onresize(), callbacks or custom code
	*/
,	resizeAll = function () {
		if (!state.initialized) {
			_initLayoutElements();
			return; // no need to resize since we just initialized!
		}
		var	oldW	= sC.innerWidth
		,	oldH	= sC.innerHeight
		;
		// cannot size layout when 'container' is hidden or collapsed
		if (!$N.is(":visible:") ) return;
		$.extend( state.container, elDims( $N ) ); // UPDATE container dimensions
		if (!sC.outerHeight) return;

		// onresizeall_start will CANCEL resizing if returns false
		// state.container has already been set, so user can access this info for calcuations
		if (false === _execCallback(null, options.onresizeall_start)) return false;

		var	// see if container is now 'smaller' than before
			shrunkH	= (sC.innerHeight < oldH)
		,	shrunkW	= (sC.innerWidth < oldW)
		,	$P, o, s, dir
		;
		// NOTE special order for sizing: S-N-E-W
		$.each(["south","north","east","west"], function (i, pane) {
			if (!$Ps[pane]) return; // no pane - SKIP
			s	= state[pane];
			o	= options[pane];
			dir	= _c[pane].dir;

			if (o.autoResize && s.size != o.size) // resize pane to original size set in options
				sizePane(pane, o.size, true, true, true); // true=skipCallback/forceResize/noAnimation
			else {
				setSizeLimits(pane);
				makePaneFit(pane, false, true, true); // true=skipCallback/forceResize
			}
		});

		sizeMidPanes("", true, true); // true=skipCallback, true=forceResize
		sizeHandles(); // reposition the toggler elements

		// trigger all individual pane callbacks AFTER layout has finished resizing
		o = options; // reuse alias
		$.each(_c.allPanes, function (i, pane) {
			$P = $Ps[pane];
			if (!$P) return; // SKIP
			if (state[pane].isVisible) { // undefined for non-existent panes
				_execCallback(pane, o[pane].onresize_end || o[pane].onresize); // callback - if exists
				resizeChildLayout(pane);
			}
		});

		_execCallback(null, o.onresizeall_end || o.onresizeall); // onresizeall callback, if exists
	}


	/**
	* Whenever a pane resizes or opens that has a nested layout, trigger resizeAll
	*
	* @param {string}	pane		The pane just resized or opened
	*/
,	resizeChildLayout = function (pane) {
		if (!options[pane].resizeChildLayout) return;
		var
			$P	= $Ps[pane]
		,	$C	= $Cs[pane]
		,	d	= "layout"
		,	P	= Instance[pane]
		,	L	= children[pane]
		;
		// user may have manually set EITHER instance pointer, so handle that
		if (P.child && !L) {
			// have to reverse the pointers!
			var el = P.child.container;
			L = children[pane] = (el ? el.data(d) : 0) || null; // set pointer _directly_ to layout instance
		}

		// if a layout-pointer exists, see if child has been destroyed
		if (L && L.destroyed)
			L = children[pane] = null; // clear child pointers
		// no child layout pointer is set - see if there is a child layout NOW
		if (!L)	L = children[pane] = $P.data(d) || ($C ? $C.data(d) : 0) || null; // set/update child pointers

		// ALWAYS refresh the pane.child alias
		P.child = children[pane];

		if (L) L.resizeAll();
	}


	/**
	* IF pane has a content-div, then resize all elements inside pane to fit pane-height
	*
	* @param {string=}	[panes=""]		The pane(s) being resized
	* @param {boolean=}	[remeasure=false]	Should the content (header/footer) be remeasured?
	*/
,	sizeContent = function (panes, remeasure) {
		if (!isInitialized()) return;

		panes = panes ? panes.split(",") : _c.allPanes;
		$.each(panes, function (idx, pane) {
			var
				$P	= $Ps[pane]
			,	$C	= $Cs[pane]
			,	o	= options[pane]
			,	s	= state[pane]
			,	m	= s.content // m = measurements
			;
			if (!$P || !$C || !$P.is(":visible")) return true; // NOT VISIBLE - skip

			// onsizecontent_start will CANCEL resizing if returns false
			if (false === _execCallback(null, o.onsizecontent_start)) return;

			// skip re-measuring offsets if live-resizing
			if ((!s.isMoving && !s.isResizing) || o.liveContentResizing || remeasure || m.top == undefined) {
				_measure();
				// if any footers are below pane-bottom, they may not measure correctly,
				// so allow pane overflow and re-measure
				if (m.hiddenFooters > 0 && $P.css("overflow") === "hidden") {
					$P.css("overflow", "visible");
					_measure(); // remeasure while overflowing
					$P.css("overflow", "hidden");
				}
			}
			// NOTE: spaceAbove/Below *includes* the pane paddingTop/Bottom, but not pane.borders
			var newH = s.innerHeight - (m.spaceAbove - s.css.paddingTop) - (m.spaceBelow - s.css.paddingBottom);

			if (!$C.is(":visible") || m.height != newH) {
				// size the Content element to fit new pane-size - will autoHide if not enough room
				setOuterHeight($C, newH, true); // true=autoHide
				m.height = newH; // save new height
			};

			if (state.initialized) {
				_execCallback(pane, o.onsizecontent_end || o.onsizecontent);
				resizeChildLayout(pane);
			}

			function _below ($E) {
				return max(s.css.paddingBottom, (parseInt($E.css("marginBottom"), 10) || 0));
			};

			function _measure () {
				var
					ignore	= options[pane].contentIgnoreSelector
				,	$Fs		= $C.nextAll().not(ignore || ':lt(0)') // not :lt(0) = ALL
				,	$Fs_vis	= $Fs.filter(':visible')
				,	$F		= $Fs_vis.filter(':last')
				;
				m = {
					top:			$C[0].offsetTop
				,	height:			$C.outerHeight()
				,	numFooters:		$Fs.length
				,	hiddenFooters:	$Fs.length - $Fs_vis.length
				,	spaceBelow:		0 // correct if no content footer ($E)
				}
					m.spaceAbove	= m.top; // just for state - not used in calc
					m.bottom		= m.top + m.height;
				if ($F.length)
					//spaceBelow = (LastFooter.top + LastFooter.height) [footerBottom] - Content.bottom + max(LastFooter.marginBottom, pane.paddingBotom)
					m.spaceBelow = ($F[0].offsetTop + $F.outerHeight()) - m.bottom + _below($F);
				else // no footer - check marginBottom on Content element itself
					m.spaceBelow = _below($C);
			};
		});
	}


	/**
	* Called every time a pane is opened, closed, or resized to slide the togglers to 'center' and adjust their length if necessary
	*
	* @see  initHandles(), open(), close(), resizeAll()
	* @param {string=}	[panes=""]		The pane(s) being resized
	*/
,	sizeHandles = function (panes) {
		panes = panes ? panes.split(",") : _c.borderPanes;

		$.each(panes, function (i, pane) {
			var 
				o	= options[pane]
			,	s	= state[pane]
			,	$P	= $Ps[pane]
			,	$R	= $Rs[pane]
			,	$T	= $Ts[pane]
			,	$TC
			;
			if (!$P || !$R) return;

			var
				dir			= _c[pane].dir
			,	_state		= (s.isClosed ? "_closed" : "_open")
			,	spacing		= o["spacing"+ _state]
			,	togAlign	= o["togglerAlign"+ _state]
			,	togLen		= o["togglerLength"+ _state]
			,	paneLen
			,	offset
			,	CSS = {}
			;

			if (spacing === 0) {
				$R.hide();
				return;
			}
			else if (!s.noRoom && !s.isHidden) // skip if resizer was hidden for any reason
				$R.show(); // in case was previously hidden

			// Resizer Bar is ALWAYS same width/height of pane it is attached to
			if (dir === "horz") { // north/south
				paneLen = $P.outerWidth(); // s.outerWidth || 
				s.resizerLength = paneLen;
				$R.css({
					width:	cssW($R, paneLen) // account for borders & padding
				,	height:	cssH($R, spacing) // ditto
				,	left:	$.layout.cssNum($P, "left")
				});
			}
			else { // east/west
				paneLen = $P.outerHeight(); // s.outerHeight || 
				s.resizerLength = paneLen;
				$R.css({
					height:	cssH($R, paneLen) // account for borders & padding
				,	width:	cssW($R, spacing) // ditto
				,	top:	sC.insetTop + getPaneSize("north", true) // TODO: what if no North pane?
				//,	top:	$.layout.cssNum($Ps["center"], "top")
				});
			}

			// remove hover classes
			removeHover( o, $R );

			if ($T) {
				if (togLen === 0 || (s.isSliding && o.hideTogglerOnSlide)) {
					$T.hide(); // always HIDE the toggler when 'sliding'
					return;
				}
				else
					$T.show(); // in case was previously hidden

				if (!(togLen > 0) || togLen === "100%" || togLen > paneLen) {
					togLen = paneLen;
					offset = 0;
				}
				else { // calculate 'offset' based on options.PANE.togglerAlign_open/closed
					if (isStr(togAlign)) {
						switch (togAlign) {
							case "top":
							case "left":	offset = 0;
											break;
							case "bottom":
							case "right":	offset = paneLen - togLen;
											break;
							case "middle":
							case "center":
							default:		offset = round((paneLen - togLen) / 2); // 'default' catches typos
						}
					}
					else { // togAlign = number
						var x = parseInt(togAlign, 10); //
						if (togAlign >= 0) offset = x;
						else offset = paneLen - togLen + x; // NOTE: x is negative!
					}
				}

				if (dir === "horz") { // north/south
					var width = cssW($T, togLen);
					$T.css({
						width:	width  // account for borders & padding
					,	height:	cssH($T, spacing) // ditto
					,	left:	offset // TODO: VERIFY that toggler  positions correctly for ALL values
					,	top:	0
					});
					// CENTER the toggler content SPAN
					$T.children(".content").each(function(){
						$TC = $(this);
						$TC.css("marginLeft", round((width-$TC.outerWidth())/2)); // could be negative
					});
				}
				else { // east/west
					var height = cssH($T, togLen);
					$T.css({
						height:	height // account for borders & padding
					,	width:	cssW($T, spacing) // ditto
					,	top:	offset // POSITION the toggler
					,	left:	0
					});
					// CENTER the toggler content SPAN
					$T.children(".content").each(function(){
						$TC = $(this);
						$TC.css("marginTop", round((height-$TC.outerHeight())/2)); // could be negative
					});
				}

				// remove ALL hover classes
				removeHover( 0, $T );
			}

			// DONE measuring and sizing this resizer/toggler, so can be 'hidden' now
			if (!state.initialized && (o.initHidden || s.noRoom)) {
				$R.hide();
				if ($T) $T.hide();
			}
		});
	}


	/**
	* @param {string}	pane
	*/
,	enableClosable = function (pane) {
		if (!isInitialized()) return;
		var $T = $Ts[pane], o = options[pane];
		if (!$T) return;
		o.closable = true;
		$T	.bind("click."+ sID, function(evt){ evt.stopPropagation(); toggle(pane); })
			.css("visibility", "visible")
			.css("cursor", "pointer")
			.attr("title", state[pane].isClosed ? o.togglerTip_closed : o.togglerTip_open) // may be blank
			.show();
	}
	/**
	* @param {string}	pane
	* @param {boolean=}	[hide=false]
	*/
,	disableClosable = function (pane, hide) {
		if (!isInitialized()) return;
		var $T = $Ts[pane];
		if (!$T) return;
		options[pane].closable = false;
		// is closable is disable, then pane MUST be open!
		if (state[pane].isClosed) open(pane, false, true);
		$T	.unbind("."+ sID)
			.css("visibility", hide ? "hidden" : "visible") // instead of hide(), which creates logic issues
			.css("cursor", "default")
			.attr("title", "");
	}


	/**
	* @param {string}	pane
	*/
,	enableSlidable = function (pane) {
		if (!isInitialized()) return;
		var $R = $Rs[pane], o = options[pane];
		if (!$R || !$R.data('draggable')) return;
		options[pane].slidable = true; 
		if (s.isClosed)
			bindStartSlidingEvent(pane, true);
	}
	/**
	* @param {string}	pane
	*/
,	disableSlidable = function (pane) {
		if (!isInitialized()) return;
		var $R = $Rs[pane];
		if (!$R) return;
		options[pane].slidable = false; 
		if (state[pane].isSliding)
			close(pane, false, true);
		else {
			bindStartSlidingEvent(pane, false);
			$R	.css("cursor", "default")
				.attr("title", "");
			removeHover(null, $R[0]); // in case currently hovered
		}
	}


	/**
	* @param {string}	pane
	*/
,	enableResizable = function (pane) {
		if (!isInitialized()) return;
		var $R = $Rs[pane], o = options[pane];
		if (!$R || !$R.data('draggable')) return;
		o.resizable = true; 
		$R.draggable("enable");
		if (!state[pane].isClosed)
			$R	.css("cursor", o.resizerCursor)
			 	.attr("title", o.resizerTip);
	}
	/**
	* @param {string}	pane
	*/
,	disableResizable = function (pane) {
		if (!isInitialized()) return;
		var $R = $Rs[pane];
		if (!$R || !$R.data('draggable')) return;
		options[pane].resizable = false; 
		$R	.draggable("disable")
			.css("cursor", "default")
			.attr("title", "");
		removeHover(null, $R[0]); // in case currently hovered
	}


	/**
	* Move a pane from source-side (eg, west) to target-side (eg, east)
	* If pane exists on target-side, move that to source-side, ie, 'swap' the panes
	*
	* @param {string}	pane1		The pane/edge being swapped
	* @param {string}	pane2		ditto
	*/
,	swapPanes = function (pane1, pane2) {
		if (!isInitialized()) return;
		// change state.edge NOW so callbacks can know where pane is headed...
		state[pane1].edge = pane2;
		state[pane2].edge = pane1;
		// run these even if NOT state.initialized
		var cancelled = false;
		if (false === _execCallback(pane1, options[pane1].onswap_start)) cancelled = true;
		if (!cancelled && false === _execCallback(pane2, options[pane2].onswap_start)) cancelled = true;
		if (cancelled) {
			state[pane1].edge = pane1; // reset
			state[pane2].edge = pane2;
			return;
		}

		var
			oPane1	= copy( pane1 )
		,	oPane2	= copy( pane2 )
		,	sizes	= {}
		;
		sizes[pane1] = oPane1 ? oPane1.state.size : 0;
		sizes[pane2] = oPane2 ? oPane2.state.size : 0;

		// clear pointers & state
		$Ps[pane1] = false; 
		$Ps[pane2] = false;
		state[pane1] = {};
		state[pane2] = {};
		
		// ALWAYS remove the resizer & toggler elements
		if ($Ts[pane1]) $Ts[pane1].remove();
		if ($Ts[pane2]) $Ts[pane2].remove();
		if ($Rs[pane1]) $Rs[pane1].remove();
		if ($Rs[pane2]) $Rs[pane2].remove();
		$Rs[pane1] = $Rs[pane2] = $Ts[pane1] = $Ts[pane2] = false;

		// transfer element pointers and data to NEW Layout keys
		move( oPane1, pane2 );
		move( oPane2, pane1 );

		// cleanup objects
		oPane1 = oPane2 = sizes = null;

		// make panes 'visible' again
		if ($Ps[pane1]) $Ps[pane1].css(_c.visible);
		if ($Ps[pane2]) $Ps[pane2].css(_c.visible);

		// fix any size discrepancies caused by swap
		resizeAll();

		// run these even if NOT state.initialized
		_execCallback(pane1, options[pane1].onswap_end || options[pane1].onswap);
		_execCallback(pane2, options[pane2].onswap_end || options[pane2].onswap);

		return;

		function copy (n) { // n = pane
			var
				$P	= $Ps[n]
			,	$C	= $Cs[n]
			;
			return !$P ? false : {
				pane:		n
			,	P:			$P ? $P[0] : false
			,	C:			$C ? $C[0] : false
			,	state:		$.extend(true, {}, state[n])
			,	options:	$.extend(true, {}, options[n])
			}
		};

		function move (oPane, pane) {
			if (!oPane) return;
			var
				P		= oPane.P
			,	C		= oPane.C
			,	oldPane = oPane.pane
			,	c		= _c[pane]
			,	side	= c.side.toLowerCase()
			,	inset	= "inset"+ c.side
			//	save pane-options that should be retained
			,	s		= $.extend({}, state[pane])
			,	o		= options[pane]
			//	RETAIN side-specific FX Settings - more below
			,	fx		= { resizerCursor: o.resizerCursor }
			,	re, size, pos
			;
			$.each("fxName,fxSpeed,fxSettings".split(","), function (i, k) {
				fx[k +"_open"]  = o[k +"_open"];
				fx[k +"_close"] = o[k +"_close"];
				fx[k +"_size"]  = o[k +"_size"];
			});

			// update object pointers and attributes
			$Ps[pane] = $(P)
				.data("layoutEdge", pane)
				.css(_c.hidden)
				.css(c.cssReq)
			;
			$Cs[pane] = C ? $(C) : false;

			// set options and state
			options[pane]	= $.extend({}, oPane.options, fx);
			state[pane]		= $.extend({}, oPane.state);

			// change classNames on the pane, eg: ui-layout-pane-east ==> ui-layout-pane-west
			re = new RegExp(o.paneClass +"-"+ oldPane, "g");
			P.className = P.className.replace(re, o.paneClass +"-"+ pane);

			// ALWAYS regenerate the resizer & toggler elements
			initHandles(pane); // create the required resizer & toggler

			// if moving to different orientation, then keep 'target' pane size
			if (c.dir != _c[oldPane].dir) {
				size = sizes[pane] || 0;
				setSizeLimits(pane); // update pane-state
				size = max(size, state[pane].minSize);
				// use manualSizePane to disable autoResize - not useful after panes are swapped
				manualSizePane(pane, size, true, true); // true/true = skipCallback/noAnimation)
			}
			else // move the resizer here
				$Rs[pane].css(side, sC[inset] + (state[pane].isVisible ? getPaneSize(pane) : 0));


			// ADD CLASSNAMES & SLIDE-BINDINGS
			if (oPane.state.isVisible && !s.isVisible)
				setAsOpen(pane, true); // true = skipCallback
			else {
				setAsClosed(pane);
				bindStartSlidingEvent(pane, true); // will enable events IF option is set
			}

			// DESTROY the object
			oPane = null;
		};
	}


	/**
	* INTERNAL method to sync pin-buttons when pane is opened or closed
	* Unpinned means the pane is 'sliding' - ie, over-top of the adjacent panes
	*
	* @see  open(), setAsOpen(), setAsClosed()
	* @param {string}	pane   These are the params returned to callbacks by layout()
	* @param {boolean}	doPin  True means set the pin 'down', False means 'up'
	*/
,	syncPinBtns = function (pane, doPin) {
		if ($.layout.plugins.buttons)
			$.each(state[pane].pins, function (i, selector) {
				$.layout.buttons.setPinState(Instance, $(selector), pane, doPin);
			});
	}

;	// END var DECLARATIONS

	/**
	* Capture keys when enableCursorHotkey - toggle pane if hotkey pressed
	*
	* @see  document.keydown()
	*/
	function keyDown (evt) {
		if (!evt) return true;
		var code = evt.keyCode;
		if (code < 33) return true; // ignore special keys: ENTER, TAB, etc

		var
			PANE = {
				38: "north" // Up Cursor	- $.ui.keyCode.UP
			,	40: "south" // Down Cursor	- $.ui.keyCode.DOWN
			,	37: "west"  // Left Cursor	- $.ui.keyCode.LEFT
			,	39: "east"  // Right Cursor	- $.ui.keyCode.RIGHT
			}
		,	ALT		= evt.altKey // no worky!
		,	SHIFT	= evt.shiftKey
		,	CTRL	= evt.ctrlKey
		,	CURSOR	= (CTRL && code >= 37 && code <= 40)
		,	o, k, m, pane
		;

		if (CURSOR && options[PANE[code]].enableCursorHotkey) // valid cursor-hotkey
			pane = PANE[code];
		else if (CTRL || SHIFT) // check to see if this matches a custom-hotkey
			$.each(_c.borderPanes, function (i, p) { // loop each pane to check its hotkey
				o = options[p];
				k = o.customHotkey;
				m = o.customHotkeyModifier; // if missing or invalid, treated as "CTRL+SHIFT"
				if ((SHIFT && m=="SHIFT") || (CTRL && m=="CTRL") || (CTRL && SHIFT)) { // Modifier matches
					if (k && code === (isNaN(k) || k <= 9 ? k.toUpperCase().charCodeAt(0) : k)) { // Key matches
						pane = p;
						return false; // BREAK
					}
				}
			});

		// validate pane
		if (!pane || !$Ps[pane] || !options[pane].closable || state[pane].isHidden)
			return true;

		toggle(pane);

		evt.stopPropagation();
		evt.returnValue = false; // CANCEL key
		return false;
	};


/*
 * ######################################
 *	UTILITY METHODS
 *	called externally or by initButtons
 * ######################################
 */

	/**
	* Change/reset a pane overflow setting & zIndex to allow popups/drop-downs to work
	*
	* @param {Object=}   [el]	(optional) Can also be 'bound' to a click, mouseOver, or other event
	*/
	function allowOverflow (el) {
		if (!isInitialized()) return;
		if (this && this.tagName) el = this; // BOUND to element
		var $P;
		if (isStr(el))
			$P = $Ps[el];
		else if ($(el).data("layoutRole"))
			$P = $(el);
		else
			$(el).parents().each(function(){
				if ($(this).data("layoutRole")) {
					$P = $(this);
					return false; // BREAK
				}
			});
		if (!$P || !$P.length) return; // INVALID

		var
			pane	= $P.data("layoutEdge")
		,	s		= state[pane]
		;

		// if pane is already raised, then reset it before doing it again!
		// this would happen if allowOverflow is attached to BOTH the pane and an element 
		if (s.cssSaved)
			resetOverflow(pane); // reset previous CSS before continuing

		// if pane is raised by sliding or resizing, or its closed, then abort
		if (s.isSliding || s.isResizing || s.isClosed) {
			s.cssSaved = false;
			return;
		}

		var
			newCSS	= { zIndex: (options.zIndexes.resizer_normal + 1) }
		,	curCSS	= {}
		,	of		= $P.css("overflow")
		,	ofX		= $P.css("overflowX")
		,	ofY		= $P.css("overflowY")
		;
		// determine which, if any, overflow settings need to be changed
		if (of != "visible") {
			curCSS.overflow = of;
			newCSS.overflow = "visible";
		}
		if (ofX && !ofX.match(/visible|auto/)) {
			curCSS.overflowX = ofX;
			newCSS.overflowX = "visible";
		}
		if (ofY && !ofY.match(/visible|auto/)) {
			curCSS.overflowY = ofX;
			newCSS.overflowY = "visible";
		}

		// save the current overflow settings - even if blank!
		s.cssSaved = curCSS;

		// apply new CSS to raise zIndex and, if necessary, make overflow 'visible'
		$P.css( newCSS );

		// make sure the zIndex of all other panes is normal
		$.each(_c.allPanes, function(i, p) {
			if (p != pane) resetOverflow(p);
		});

	};
	/**
	* @param {Object=}   [el]	(optional) Can also be 'bound' to a click, mouseOver, or other event
	*/
	function resetOverflow (el) {
		if (!isInitialized()) return;
		if (this && this.tagName) el = this; // BOUND to element
		var $P;
		if (isStr(el))
			$P = $Ps[el];
		else if ($(el).data("layoutRole"))
			$P = $(el);
		else
			$(el).parents().each(function(){
				if ($(this).data("layoutRole")) {
					$P = $(this);
					return false; // BREAK
				}
			});
		if (!$P || !$P.length) return; // INVALID

		var
			pane	= $P.data("layoutEdge")
		,	s		= state[pane]
		,	CSS		= s.cssSaved || {}
		;
		// reset the zIndex
		if (!s.isSliding && !s.isResizing)
			$P.css("zIndex", options.zIndexes.pane_normal);

		// reset Overflow - if necessary
		$P.css( CSS );

		// clear var
		s.cssSaved = false;
	};

/*
 * #####################
 * CREATE/RETURN LAYOUT
 * #####################
 */

	// validate that container exists
	var $N = $(this).eq(0); // FIRST matching Container element
	if (!$N.length) {
		if (options.showErrorMessages)
			_log( lang.errContainerMissing, true );
		return null;
	};

	// Users retrieve Instance of a layout with: $N.layout() OR $N.data("layout")
	// return the Instance-pointer if layout has already been initialized
	if ($N.data("layoutContainer") && $N.data("layout"))
		return $N.data("layout"); // cached pointer

	// init global vars
	var 
		$Ps	= {}	// Panes x5		- set in initPanes()
	,	$Cs	= {}	// Content x5	- set in initPanes()
	,	$Rs	= {}	// Resizers x4	- set in initHandles()
	,	$Ts	= {}	// Togglers x4	- set in initHandles()
	,	$Ms	= $([])	// Masks - up to 2 masks per pane (IFRAME + DIV)
	//	aliases for code brevity
	,	sC	= state.container // alias for easy access to 'container dimensions'
	,	sID	= state.id // alias for unique layout ID/namespace - eg: "layout435"
	;

	// create Instance object to expose data & option Properties, and primary action Methods
	var Instance = {
	//	layout data
		options:			options			// property - options hash
	,	state:				state			// property - dimensions hash
	//	object pointers
	,	container:			$N				// property - object pointers for layout container
	,	panes:				$Ps				// property - object pointers for ALL Panes: panes.north, panes.center
	,	contents:			$Cs				// property - object pointers for ALL Content: contents.north, contents.center
	,	resizers:			$Rs				// property - object pointers for ALL Resizers, eg: resizers.north
	,	togglers:			$Ts				// property - object pointers for ALL Togglers, eg: togglers.north
	//	border-pane open/close
	,	hide:				hide			// method - ditto
	,	show:				show			// method - ditto
	,	toggle:				toggle			// method - pass a 'pane' ("north", "west", etc)
	,	open:				open			// method - ditto
	,	close:				close			// method - ditto
	,	slideOpen:			slideOpen		// method - ditto
	,	slideClose:			slideClose		// method - ditto
	,	slideToggle:		slideToggle		// method - ditto
	//	pane actions
	,	setSizeLimits:		setSizeLimits	// method - pass a 'pane' - update state min/max data
	,	_sizePane:			sizePane		// method -intended for user by plugins only!
	,	sizePane:			manualSizePane	// method - pass a 'pane' AND an 'outer-size' in pixels or percent, or 'auto'
	,	sizeContent:		sizeContent		// method - ditto
	,	swapPanes:			swapPanes		// method - pass TWO 'panes' - will swap them
	//	layout control
	,	createChildLayout:	createChildLayout// method - pass a 'pane' and (optional) layout-options (OVERRIDES options[pane].childOptions
	,	destroy:			destroy			// method - no parameters
	,	addPane:			addPane			// method - pass a 'pane'
	,	removePane:			removePane		// method - pass a 'pane' to remove from layout, add 'true' to delete the pane-elem
	,	initPanes:			isInitialized	// method - no parameters
	,	initContent:		initContent		// method - ditto
	,	resizeAll:			resizeAll		// method - no parameters
	,	allowOverflow:		allowOverflow	// utility - pass calling element (this)
	,	resetOverflow:		resetOverflow	// utility - ditto
	//	special option setting
	,	enableClosable:		enableClosable
	,	disableClosable:	disableClosable
	,	enableSlidable:		enableSlidable
	,	disableSlidable:	disableSlidable
	,	enableResizable:	enableResizable
	,	disableResizable:	disableResizable
	//	event triggering
	,	trigger:			trigger
	//	alias collections of options, state and children - created in addPane and extended elsewhere
	,	hasParentLayout:	false
	,	children:			children
	,	north:				false
	,	south:				false
	,	west:				false
	,	east:				false
	,	center:				false
	};

	// create the border layout NOW
	if (_create() === 'cancel') // onload_start callback returned false to CANCEL layout creation
		return null;
	else // true OR false -- if layout-elements did NOT init (hidden or do not exist), can auto-init later
		return Instance; // return the Instance object

}




/**
 * jquery.layout.state 1.0
 * $Date: 2011-07-16 08:00:00 (Sat, 16 July 2011) $
 *
 * Copyright (c) 2010 
 *   Kevin Dalman (http://allpro.net)
 *
 * Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html)
 * and MIT (http://www.opensource.org/licenses/mit-license.php) licenses.
 *
 * @dependancies: UI Layout 1.3.0.rc30.1 or higher
 * @dependancies: $.ui.cookie (above)
 *
 * @support: http://groups.google.com/group/jquery-ui-layout
 */
/*
 *	State-management options stored in options.stateManagement, which includes a .cookie hash
 *	Default options saves ALL KEYS for ALL PANES, ie: pane.size, pane.isClosed, pane.isHidden
 *
 *	// STATE/COOKIE OPTIONS
 *	@example $(el).layout({
				stateManagement: {
					enabled:	true
				,	stateKeys:	"east.size,west.size,east.isClosed,west.isClosed"
				,	cookie:		{ name: "appLayout", path: "/" }
				}
			})
 *	@example $(el).layout({ stateManagement__enabled: true }) // enable auto-state-management using cookies
 *	@example $(el).layout({ stateManagement__cookie: { name: "appLayout", path: "/" } })
 *	@example $(el).layout({ stateManagement__cookie__name: "appLayout", stateManagement__cookie__path: "/" })
 *
 *	// STATE/COOKIE METHODS
 *	@example myLayout.saveCookie( "west.isClosed,north.size,south.isHidden", {expires: 7} );
 *	@example myLayout.loadCookie();
 *	@example myLayout.deleteCookie();
 *	@example var JSON = myLayout.readState();	// CURRENT Layout State
 *	@example var JSON = myLayout.readCookie();	// SAVED Layout State (from cookie)
 *	@example var JSON = myLayout.state.stateData;	// LAST LOADED Layout State (cookie saved in layout.state hash)
 *
 *	CUSTOM STATE-MANAGEMENT (eg, saved in a database)
 *	@example var JSON = myLayout.readState( "west.isClosed,north.size,south.isHidden" );
 *	@example myLayout.loadState( JSON );
 */

/**
 *	UI COOKIE UTILITY
 *
 *	A $.cookie OR $.ui.cookie namespace *should be standard*, but until then...
 *	This creates $.ui.cookie so Layout does not need the cookie.jquery.js plugin
 *	NOTE: This utility is REQUIRED by the layout.state plugin
 *
 *	Cookie methods in Layout are created as part of State Management 
 */
if (!$.ui) $.ui = {};
$.ui.cookie = {

	// TODO: is the cookieEnabled property fully cross-browser???
	acceptsCookies: !!navigator.cookieEnabled

,	read: function (name) {
		var
			c		= document.cookie
		,	cs		= c ? c.split(';') : []
		,	pair	// loop var
		;
		for (var i=0, n=cs.length; i < n; i++) {
			pair = $.trim(cs[i]).split('='); // name=value pair
			if (pair[0] == name) // found the layout cookie
				return decodeURIComponent(pair[1]);

		}
		return null;
	}

,	write: function (name, val, cookieOpts) {
		var
			params	= ''
		,	date	= ''
		,	clear	= false
		,	o		= cookieOpts || {}
		,	x		= o.expires
		;
		if (x && x.toUTCString)
			date = x;
		else if (x === null || typeof x === 'number') {
			date = new Date();
			if (x > 0)
				date.setDate(date.getDate() + x);
			else {
				date.setFullYear(1970);
				clear = true;
			}
		}
		if (date)		params += ';expires='+ date.toUTCString();
		if (o.path)		params += ';path='+ o.path;
		if (o.domain)	params += ';domain='+ o.domain;
		if (o.secure)	params += ';secure';
		document.cookie = name +'='+ (clear ? "" : encodeURIComponent( val )) + params; // write or clear cookie
	}

,	clear: function (name) {
		$.ui.cookie.write(name, '', {expires: -1});
	}

};
// if cookie.jquery.js is not loaded, create an alias to replicate it
// this may be useful to other plugins or code dependent on that plugin
if (!$.cookie) $.cookie = function (k, v, o) {
	var C = $.ui.cookie;
	if (v === null)
		C.clear(k);
	else if (v === undefined)
		return C.read(k);
	else
		C.write(k, v, o);
};


// tell Layout that the state plugin is available
$.layout.plugins.stateManagement = true;

//	Add State-Management options to layout.defaults
$.layout.config.optionRootKeys.push("stateManagement");
$.layout.defaults.stateManagement = {
	enabled:	false	// true = enable state-management, even if not using cookies
,	autoSave:	true	// Save a state-cookie when page exits?
,	autoLoad:	true	// Load the state-cookie when Layout inits?
	// List state-data to save - must be pane-specific
,	stateKeys:	"north.size,south.size,east.size,west.size,"+
				"north.isClosed,south.isClosed,east.isClosed,west.isClosed,"+
				"north.isHidden,south.isHidden,east.isHidden,west.isHidden"
,	cookie: {
		name:	""	// If not specified, will use Layout.name, else just "Layout"
	,	domain:	""	// blank = current domain
	,	path:	""	// blank = current page, '/' = entire website
	,	expires: ""	// 'days' to keep cookie - leave blank for 'session cookie'
	,	secure:	false
	}
};
// Set stateManagement as a layout-option, NOT a pane-option
$.layout.optionsMap.layout.push("stateManagement");

/*
 *	State Management methods
 */
$.layout.state = {

	/**
	 * Get the current layout state and save it to a cookie
	 *
	 * myLayout.saveCookie( keys, cookieOpts )
	 *
	 * @param {Object}			inst
	 * @param {(string|Array)=}	keys
	 * @param {Object=}			opts
	 */
	saveCookie: function (inst, keys, cookieOpts) {
		var o	= inst.options
		,	oS	= o.stateManagement
		,	oC	= $.extend(true, {}, oS.cookie, cookieOpts || null)
		,	data = inst.state.stateData = inst.readState( keys || oS.stateKeys ) // read current panes-state
		;
		$.ui.cookie.write( oC.name || o.name || "Layout", $.layout.state.encodeJSON(data), oC );
		return $.extend(true, {}, data); // return COPY of state.stateData data
	}

	/**
	 * Remove the state cookie
	 *
	 * @param {Object}	inst
	 */
,	deleteCookie: function (inst) {
		var o = inst.options;
		$.ui.cookie.clear( o.stateManagement.cookie.name || o.name || "Layout" );
	}

	/**
	 * Read & return data from the cookie - as JSON
	 *
	 * @param {Object}	inst
	 */
,	readCookie: function (inst) {
		var o = inst.options;
		var c = $.ui.cookie.read( o.stateManagement.cookie.name || o.name || "Layout" );
		// convert cookie string back to a hash and return it
		return c ? $.layout.state.decodeJSON(c) : {};
	}

	/**
	 * Get data from the cookie and USE IT to loadState
	 *
	 * @param {Object}	inst
	 */
,	loadCookie: function (inst) {
		var c = $.layout.state.readCookie(inst); // READ the cookie
		if (c) {
			inst.state.stateData = $.extend(true, {}, c); // SET state.stateData
			inst.loadState(c); // LOAD the retrieved state
		}
		return c;
	}
	
	/**
	 * Update layout options from the cookie, if one exists
	 *
	 * @param {Object}		inst
	 * @param {Object=}		stateData
	 * @param {boolean=}	animate
	 */
,	loadState: function (inst, stateData, animate) {
		stateData = $.layout.transformData( stateData ); // panes = default subkey
		if ($.isEmptyObject( stateData )) return;
		$.extend(true, inst.options, stateData); // update layout options
		// if layout has already been initialized, then UPDATE layout state
		if (inst.state.initialized) {
			var pane, vis, o, s, h, c
			,	noAnimate = (animate===false)
			;
			$.each($.layout.config.borderPanes, function (idx, pane) {
				state = inst.state[pane];
				o = stateData[ pane ];
				if (typeof o != 'object') return; // no key, continue
				s	= o.size;
				c	= o.initClosed;
				h	= o.initHidden;
				vis	= state.isVisible;
				// resize BEFORE opening
				// resize BEFORE opening
				if (!vis)
					inst.sizePane(pane, s, false, false);
				if (h === true)			inst.hide(pane, noAnimate);
				else if (c === false)	inst.open (pane, false, noAnimate);
				else if (c === true)	inst.close(pane, false, noAnimate);
				else if (h === false)	inst.show (pane, false, noAnimate);
				// resize AFTER any other actions
				if (vis)
					inst.sizePane(pane, s, false, noAnimate); // animate resize if option passed
			});
		};
	}

	/**
	 * Get the *current layout state* and return it as a hash
	 *
	 * @param {Object=}			inst
	 * @param {(string|Array)=}	keys
	 */
,	readState: function (inst, keys) {
		var
			data	= {}
		,	alt		= { isClosed: 'initClosed', isHidden: 'initHidden' }
		,	state	= inst.state
		,	panes	= $.layout.config.allPanes
		,	pair, pane, key, val
		;
		if (!keys) keys = inst.options.stateManagement.stateKeys; // if called by user
		if ($.isArray(keys)) keys = keys.join(",");
		// convert keys to an array and change delimiters from '__' to '.'
		keys = keys.replace(/__/g, ".").split(',');
		// loop keys and create a data hash
		for (var i=0, n=keys.length; i < n; i++) {
			pair = keys[i].split(".");
			pane = pair[0];
			key  = pair[1];
			if ($.inArray(pane, panes) < 0) continue; // bad pane!
			val = state[ pane ][ key ];
			if (val == undefined) continue;
			if (key=="isClosed" && state[pane]["isSliding"])
				val = true; // if sliding, then *really* isClosed
			( data[pane] || (data[pane]={}) )[ alt[key] ? alt[key] : key ] = val;
		}
		return data;
	}

	/**
	 *	Stringify a JSON hash so can save in a cookie or db-field
	 */
,	encodeJSON: function (JSON) {
		return parse(JSON);
		function parse (h) {
			var D=[], i=0, k, v, t; // k = key, v = value
			for (k in h) {
				v = h[k];
				t = typeof v;
				if (t == 'string')		// STRING - add quotes
					v = '"'+ v +'"';
				else if (t == 'object')	// SUB-KEY - recurse into it
					v = parse(v);
				D[i++] = '"'+ k +'":'+ v;
			}
			return '{'+ D.join(',') +'}';
		};
	}

	/**
	 *	Convert stringified JSON back to a hash object
	 *	@see		$.parseJSON(), adding in jQuery 1.4.1
	 */
,	decodeJSON: function (str) {
		try { return $.parseJSON ? $.parseJSON(str) : window["eval"]("("+ str +")") || {}; }
		catch (e) { return {}; }
	}


,	_create: function (inst) {
		var _	= $.layout.state;
		//	ADD State-Management plugin methods to inst
		 $.extend( inst, {
		//	readCookie - update options from cookie - returns hash of cookie data
			readCookie:		function () { return _.readCookie(inst); }
		//	deleteCookie
		,	deleteCookie:	function () { _.deleteCookie(inst); }
		//	saveCookie - optionally pass keys-list and cookie-options (hash)
		,	saveCookie:		function (keys, cookieOpts) { return _.saveCookie(inst, keys, cookieOpts); }
		//	loadCookie - readCookie and use to loadState() - returns hash of cookie data
		,	loadCookie:		function () { return _.loadCookie(inst); }
		//	loadState - pass a hash of state to use to update options
		,	loadState:		function (stateData, animate) { _.loadState(inst, stateData, animate); }
		//	readState - returns hash of current layout-state
		,	readState:		function (keys) { return _.readState(inst, keys); }
		//	add JSON utility methods too...
		,	encodeJSON:		_.encodeJSON
		,	decodeJSON:		_.decodeJSON
		});

		// init state.stateData key, even if plugin is initially disabled
		inst.state.stateData = {};

		// read and load cookie-data per options
		var oS = inst.options.stateManagement;
		if (oS.enabled) {
			if (oS.autoLoad) // update the options from the cookie
				inst.loadCookie();
			else // don't modify options - just store cookie data in state.stateData
				inst.state.stateData = inst.readCookie();
		}
	}

,	_unload: function (inst) {
		var oS = inst.options.stateManagement;
		if (oS.enabled) {
			if (oS.autoSave) // save a state-cookie automatically
				inst.saveCookie();
			else // don't save a cookie, but do store state-data in state.stateData key
				inst.state.stateData = inst.readState();
		}
	}

};

// add state initialization method to Layout's onCreate array of functions
$.layout.onCreate.push( $.layout.state._create );
$.layout.onUnload.push( $.layout.state._unload );




/**
 * jquery.layout.buttons 1.0
 * $Date: 2011-07-16 08:00:00 (Sat, 16 July 2011) $
 *
 * Copyright (c) 2010 
 *   Kevin Dalman (http://allpro.net)
 *
 * Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html)
 * and MIT (http://www.opensource.org/licenses/mit-license.php) licenses.
 *
 * @dependancies: UI Layout 1.3.0.rc30.1 or higher
 *
 * @support: http://groups.google.com/group/jquery-ui-layout
 *
 * Docs: [ to come ]
 * Tips: [ to come ]
 */

// tell Layout that the state plugin is available
$.layout.plugins.buttons = true;

//	Add buttons options to layout.defaults
$.layout.defaults.autoBindCustomButtons = false;
// Specify autoBindCustomButtons as a layout-option, NOT a pane-option
$.layout.optionsMap.layout.push("autoBindCustomButtons");

var lang = $.layout.language;

/*
 *	Button methods
 */
$.layout.buttons = {

	/**
	* Searches for .ui-layout-button-xxx elements and auto-binds them as layout-buttons
	*
	* @see  _create()
	*
	* @param  {Object}		inst	Layout Instance object
	*/
	init: function (inst) {
		var pre		= "ui-layout-button-"
		,	layout	= inst.options.name || ""
		,	name;
		$.each("toggle,open,close,pin,toggle-slide,open-slide".split(","), function (i, action) {
			$.each($.layout.config.borderPanes, function (ii, pane) {
				$("."+pre+action+"-"+pane).each(function(){
					// if button was previously 'bound', data.layoutName was set, but is blank if layout has no 'name'
					name = $(this).data("layoutName") || $(this).attr("layoutName");
					if (name == undefined || name === layout)
						inst.bindButton(this, action, pane);
				});
			});
		});
	}

	/**
	* Helper function to validate params received by addButton utilities
	*
	* Two classes are added to the element, based on the buttonClass...
	* The type of button is appended to create the 2nd className:
	*  - ui-layout-button-pin		// action btnClass
	*  - ui-layout-button-pin-west	// action btnClass + pane
	*  - ui-layout-button-toggle
	*  - ui-layout-button-open
	*  - ui-layout-button-close
	*
	* @param {Object}			inst		Layout Instance object
	* @param {(string|!Object)}	selector	jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
	* @param {string}   		pane 		Name of the pane the button is for: 'north', 'south', etc.
	*
	* @return {Array.<Object>}	If both params valid, the element matching 'selector' in a jQuery wrapper - otherwise returns null
	*/
,	get: function (inst, selector, pane, action) {
		var $E	= $(selector)
		,	o	= inst.options
		,	err	= o.showErrorMessages
		;
		if (!$E.length) { // element not found
			if (err) $.layout.msg(lang.errButton + lang.selector +": "+ selector, true);
		}
		else if ($.inArray(pane, $.layout.config.borderPanes) < 0) { // invalid 'pane' sepecified
			if (err) $.layout.msg(lang.errButton + lang.pane +": "+ pane, true);
			$E = $("");  // NO BUTTON
		}
		else { // VALID
			var btn = o[pane].buttonClass +"-"+ action;
			$E	.addClass( btn +" "+ btn +"-"+ pane )
				.data("layoutName", o.name); // add layout identifier - even if blank!
		}
		return $E;
	}


	/**
	* NEW syntax for binding layout-buttons - will eventually replace addToggle, addOpen, etc.
	*
	* @param {Object}			inst		Layout Instance object
	* @param {(string|!Object)}	selector	jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
	* @param {string}			action
	* @param {string}			pane
	*/
,	bind: function (inst, selector, action, pane) {
		var _ = $.layout.buttons;
		switch (action.toLowerCase()) {
			case "toggle":			_.addToggle	(inst, selector, pane); break;	
			case "open":			_.addOpen	(inst, selector, pane); break;
			case "close":			_.addClose	(inst, selector, pane); break;
			case "pin":				_.addPin	(inst, selector, pane); break;
			case "toggle-slide":	_.addToggle	(inst, selector, pane, true); break;	
			case "open-slide":		_.addOpen	(inst, selector, pane, true); break;
		}
		return inst;
	}

	/**
	* Add a custom Toggler button for a pane
	*
	* @param {Object}			inst		Layout Instance object
	* @param {(string|!Object)}	selector	jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
	* @param {string}  			pane 		Name of the pane the button is for: 'north', 'south', etc.
	* @param {boolean=}			slide 		true = slide-open, false = pin-open
	*/
,	addToggle: function (inst, selector, pane, slide) {
		$.layout.buttons.get(inst, selector, pane, "toggle")
			.click(function(evt){
				inst.toggle(pane, !!slide);
				evt.stopPropagation();
			});
		return inst;
	}

	/**
	* Add a custom Open button for a pane
	*
	* @param {Object}			inst		Layout Instance object
	* @param {(string|!Object)}	selector	jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
	* @param {string}			pane 		Name of the pane the button is for: 'north', 'south', etc.
	* @param {boolean=}			slide 		true = slide-open, false = pin-open
	*/
,	addOpen: function (inst, selector, pane, slide) {
		$.layout.buttons.get(inst, selector, pane, "open")
			.attr("title", lang.Open)
			.click(function (evt) {
				inst.open(pane, !!slide);
				evt.stopPropagation();
			});
		return inst;
	}

	/**
	* Add a custom Close button for a pane
	*
	* @param {Object}			inst		Layout Instance object
	* @param {(string|!Object)}	selector	jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
	* @param {string}   		pane 		Name of the pane the button is for: 'north', 'south', etc.
	*/
,	addClose: function (inst, selector, pane) {
		$.layout.buttons.get(inst, selector, pane, "close")
			.attr("title", lang.Close)
			.click(function (evt) {
				inst.close(pane);
				evt.stopPropagation();
			});
		return inst;
	}

	/**
	* Add a custom Pin button for a pane
	*
	* Four classes are added to the element, based on the paneClass for the associated pane...
	* Assuming the default paneClass and the pin is 'up', these classes are added for a west-pane pin:
	*  - ui-layout-pane-pin
	*  - ui-layout-pane-west-pin
	*  - ui-layout-pane-pin-up
	*  - ui-layout-pane-west-pin-up
	*
	* @param {Object}			inst		Layout Instance object
	* @param {(string|!Object)}	selector	jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
	* @param {string}   		pane 		Name of the pane the pin is for: 'north', 'south', etc.
	*/
,	addPin: function (inst, selector, pane) {
		var	_	= $.layout.buttons
		,	$E	= _.get(inst, selector, pane, "pin");
		if ($E.length) {
			var s = inst.state[pane];
			$E.click(function (evt) {
				_.setPinState(inst, $(this), pane, (s.isSliding || s.isClosed));
				if (s.isSliding || s.isClosed) inst.open( pane ); // change from sliding to open
				else inst.close( pane ); // slide-closed
				evt.stopPropagation();
			});
			// add up/down pin attributes and classes
			_.setPinState(inst, $E, pane, (!s.isClosed && !s.isSliding));
			// add this pin to the pane data so we can 'sync it' automatically
			// PANE.pins key is an array so we can store multiple pins for each pane
			s.pins.push( selector ); // just save the selector string
		}
		return inst;
	}

	/**
	* Change the class of the pin button to make it look 'up' or 'down'
	*
	* @see  addPin(), syncPins()
	*
	* @param {Object}			inst	Layout Instance object
	* @param {Array.<Object>}	$Pin	The pin-span element in a jQuery wrapper
	* @param {string}			pane	These are the params returned to callbacks by layout()
	* @param {boolean}			doPin	true = set the pin 'down', false = set it 'up'
	*/
,	setPinState: function (inst, $Pin, pane, doPin) {
		var updown = $Pin.attr("pin");
		if (updown && doPin === (updown=="down")) return; // already in correct state
		var
			pin		= inst.options[pane].buttonClass +"-pin"
		,	side	= pin +"-"+ pane
		,	UP		= pin +"-up "+	side +"-up"
		,	DN		= pin +"-down "+side +"-down"
		;
		$Pin
			.attr("pin", doPin ? "down" : "up") // logic
			.attr("title", doPin ? lang.Unpin : lang.Pin)
			.removeClass( doPin ? UP : DN ) 
			.addClass( doPin ? DN : UP ) 
		;
	}

	/**
	* INTERNAL function to sync 'pin buttons' when pane is opened or closed
	* Unpinned means the pane is 'sliding' - ie, over-top of the adjacent panes
	*
	* @see  open(), close()
	*
	* @param {Object}			inst	Layout Instance object
	* @param {string}	pane	These are the params returned to callbacks by layout()
	* @param {boolean}	doPin	True means set the pin 'down', False means 'up'
	*/
,	syncPinBtns: function (inst, pane, doPin) {
		// REAL METHOD IS _INSIDE_ LAYOUT - THIS IS HERE JUST FOR REFERENCE
		$.each(state[pane].pins, function (i, selector) {
			$.layout.buttons.setPinState(inst, $(selector), pane, doPin);
		});
	}


,	_load: function (inst) {
		var	_	= $.layout.buttons;
		// ADD Button methods to Layout Instance
		// Note: sel = jQuery Selector string
		$.extend( inst, {
			bindButton:		function (sel, action, pane) { return _.bind(inst, sel, action, pane); }
		//	DEPRECATED METHODS
		,	addToggleBtn:	function (sel, pane, slide) { return _.addToggle(inst, sel, pane, slide); }
		,	addOpenBtn:		function (sel, pane, slide) { return _.addOpen(inst, sel, pane, slide); }
		,	addCloseBtn:	function (sel, pane) { return _.addClose(inst, sel, pane); }
		,	addPinBtn:		function (sel, pane) { return _.addPin(inst, sel, pane); }
		});

		// init state array to hold pin-buttons
		for (var i=0; i<4; i++) {
			var pane = $.layout.config.borderPanes[i];
			inst.state[pane].pins = [];
		}

		// auto-init buttons onLoad if option is enabled
		if ( inst.options.autoBindCustomButtons )
			_.init(inst);
	}

,	_unload: function (inst) {
		// TODO: unbind all buttons???
	}

};

// add initialization method to Layout's onLoad array of functions
$.layout.onLoad.push(  $.layout.buttons._load );
//$.layout.onUnload.push( $.layout.buttons._unload );



/**
 * jquery.layout.browserZoom 1.0
 * $Date: 2011-12-29 08:00:00 (Thu, 29 Dec 2011) $
 *
 * Copyright (c) 2012 
 *   Kevin Dalman (http://allpro.net)
 *
 * Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html)
 * and MIT (http://www.opensource.org/licenses/mit-license.php) licenses.
 *
 * @dependancies: UI Layout 1.3.0.rc30.1 or higher
 *
 * @support: http://groups.google.com/group/jquery-ui-layout
 *
 * @todo: Extend logic to handle other problematic zooming in browsers
 * @todo: Add hotkey/mousewheel bindings to _instantly_ respond to these zoom event
 */

// tell Layout that the plugin is available
$.layout.plugins.browserZoom = true;

$.layout.defaults.browserZoomCheckInterval = 1000;
$.layout.optionsMap.layout.push("browserZoomCheckInterval");

/*
 *	browserZoom methods
 */
$.layout.browserZoom = {

	_init: function (inst) {
		$.layout.browserZoom._setTimer(inst);
	}

,	_setTimer: function (inst) {
		if (inst.destroyed) return;
		var o = inst.options
		,	s = inst.state
		,	z = s.browserZoom = $.layout.browserZoom.ratio()
		;
		if (o.resizeWithWindow && z !== false) {
			setTimeout(function(){
				if (inst.destroyed) return;
				var d = $.layout.browserZoom.ratio();
				if (d !== s.browserZoom) {
					s.browserZoom = d;
					inst.resizeAll();
				}
				$.layout.browserZoom._setTimer(inst); // set a NEW timeout
			},	Math.max( o.browserZoomCheckInterval, 100 )); // MINIMUM 100ms interval, for performance
		}
	}

,	ratio: function () {
		var w	= window
		,	s	= screen
		,	d	= document
		,	dE	= d.documentElement || d.body
		,	b	= $.layout.browser
		,	v	= b.version
		,	r, sW, cW
		;
		// we can ignore all browsers that fire window.resize event onZoom
		if ((b.msie && v > 8)
		||	!b.msie
		) return false; // don't need to track zoom

		if (s.deviceXDPI)
			return calc(s.deviceXDPI, s.systemXDPI);
		// everything below is just for future reference!
		if (b.webkit && (r = d.body.getBoundingClientRect))
			return calc((r.left - r.right), d.body.offsetWidth);
		if (b.webkit && (sW = w.outerWidth))
			return calc(sW, w.innerWidth);
		if ((sW = s.width) && (cW = dE.clientWidth))
			return calc(sW, cW);
		return false; // no match, so cannot - or don't need to - track zoom

		function calc (x,y) { return (parseInt(x,10) / parseInt(y,10) * 100).toFixed(); }
	}

};
// add initialization method to Layout's onLoad array of functions
$.layout.onReady.push( $.layout.browserZoom._init );



})( jQuery );
console.log('=============== >  jquery.layout-latest.js ');

/*
    http://www.JSON.org/json2.js
    2011-02-23

    Public Domain.

    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.

    See http://www.JSON.org/js.html


    This code should be minified before deployment.
    See http://javascript.crockford.com/jsmin.html

    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
    NOT CONTROL.


    This file creates a global JSON object containing two methods: stringify
    and parse.

        JSON.stringify(value, replacer, space)
            value       any JavaScript value, usually an object or array.

            replacer    an optional parameter that determines how object
                        values are stringified for objects. It can be a
                        function or an array of strings.

            space       an optional parameter that specifies the indentation
                        of nested structures. If it is omitted, the text will
                        be packed without extra whitespace. If it is a number,
                        it will specify the number of spaces to indent at each
                        level. If it is a string (such as '\t' or '&nbsp;'),
                        it contains the characters used to indent at each level.

            This method produces a JSON text from a JavaScript value.

            When an object value is found, if the object contains a toJSON
            method, its toJSON method will be called and the result will be
            stringified. A toJSON method does not serialize: it returns the
            value represented by the name/value pair that should be serialized,
            or undefined if nothing should be serialized. The toJSON method
            will be passed the key associated with the value, and this will be
            bound to the value

            For example, this would serialize Dates as ISO strings.

                Date.prototype.toJSON = function (key) {
                    function f(n) {
                        // Format integers to have at least two digits.
                        return n < 10 ? '0' + n : n;
                    }

                    return this.getUTCFullYear()   + '-' +
                         f(this.getUTCMonth() + 1) + '-' +
                         f(this.getUTCDate())      + 'T' +
                         f(this.getUTCHours())     + ':' +
                         f(this.getUTCMinutes())   + ':' +
                         f(this.getUTCSeconds())   + 'Z';
                };

            You can provide an optional replacer method. It will be passed the
            key and value of each member, with this bound to the containing
            object. The value that is returned from your method will be
            serialized. If your method returns undefined, then the member will
            be excluded from the serialization.

            If the replacer parameter is an array of strings, then it will be
            used to select the members to be serialized. It filters the results
            such that only members with keys listed in the replacer array are
            stringified.

            Values that do not have JSON representations, such as undefined or
            functions, will not be serialized. Such values in objects will be
            dropped; in arrays they will be replaced with null. You can use
            a replacer function to replace those with JSON values.
            JSON.stringify(undefined) returns undefined.

            The optional space parameter produces a stringification of the
            value that is filled with line breaks and indentation to make it
            easier to read.

            If the space parameter is a non-empty string, then that string will
            be used for indentation. If the space parameter is a number, then
            the indentation will be that many spaces.

            Example:

            text = JSON.stringify(['e', {pluribus: 'unum'}]);
            // text is '["e",{"pluribus":"unum"}]'


            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'

            text = JSON.stringify([new Date()], function (key, value) {
                return this[key] instanceof Date ?
                    'Date(' + this[key] + ')' : value;
            });
            // text is '["Date(---current time---)"]'


        JSON.parse(text, reviver)
            This method parses a JSON text to produce an object or array.
            It can throw a SyntaxError exception.

            The optional reviver parameter is a function that can filter and
            transform the results. It receives each of the keys and values,
            and its return value is used instead of the original value.
            If it returns what it received, then the structure is not modified.
            If it returns undefined then the member is deleted.

            Example:

            // Parse the text. Values that look like ISO date strings will
            // be converted to Date objects.

            myData = JSON.parse(text, function (key, value) {
                var a;
                if (typeof value === 'string') {
                    a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
                    if (a) {
                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
                            +a[5], +a[6]));
                    }
                }
                return value;
            });

            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
                var d;
                if (typeof value === 'string' &&
                        value.slice(0, 5) === 'Date(' &&
                        value.slice(-1) === ')') {
                    d = new Date(value.slice(5, -1));
                    if (d) {
                        return d;
                    }
                }
                return value;
            });


    This is a reference implementation. You are free to copy, modify, or
    redistribute.
*/

/*jslint evil: true, strict: false, regexp: false */

/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
    lastIndex, length, parse, prototype, push, replace, slice, stringify,
    test, toJSON, toString, valueOf
*/


// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.

var JSON;
if (!JSON) {
    JSON = {};
}

(function () {
    "use strict";

    function f(n) {
        // Format integers to have at least two digits.
        return n < 10 ? '0' + n : n;
    }

    if (typeof Date.prototype.toJSON !== 'function') {

        Date.prototype.toJSON = function (key) {

            return isFinite(this.valueOf()) ?
                this.getUTCFullYear()     + '-' +
                f(this.getUTCMonth() + 1) + '-' +
                f(this.getUTCDate())      + 'T' +
                f(this.getUTCHours())     + ':' +
                f(this.getUTCMinutes())   + ':' +
                f(this.getUTCSeconds())   + 'Z' : null;
        };

        String.prototype.toJSON      =
            Number.prototype.toJSON  =
            Boolean.prototype.toJSON = function (key) {
                return this.valueOf();
            };
    }

    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        gap,
        indent,
        meta = {    // table of character substitutions
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        },
        rep;


    function quote(string) {

// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.

        escapable.lastIndex = 0;
        return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
            var c = meta[a];
            return typeof c === 'string' ? c :
                '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
        }) + '"' : '"' + string + '"';
    }


    function str(key, holder) {

// Produce a string from holder[key].

        var i,          // The loop counter.
            k,          // The member key.
            v,          // The member value.
            length,
            mind = gap,
            partial,
            value = holder[key];

// If the value has a toJSON method, call it to obtain a replacement value.

        if (value && typeof value === 'object' &&
                typeof value.toJSON === 'function') {
            value = value.toJSON(key);
        }

// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.

        if (typeof rep === 'function') {
            value = rep.call(holder, key, value);
        }

// What happens next depends on the value's type.

        switch (typeof value) {
        case 'string':
            return quote(value);

        case 'number':

// JSON numbers must be finite. Encode non-finite numbers as null.

            return isFinite(value) ? String(value) : 'null';

        case 'boolean':
        case 'null':

// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.

            return String(value);

// If the type is 'object', we might be dealing with an object or an array or
// null.

        case 'object':

// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.

            if (!value) {
                return 'null';
            }

// Make an array to hold the partial results of stringifying this object value.

            gap += indent;
            partial = [];

// Is the value an array?

            if (Object.prototype.toString.apply(value) === '[object Array]') {

// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.

                length = value.length;
                for (i = 0; i < length; i += 1) {
                    partial[i] = str(i, value) || 'null';
                }

// Join all of the elements together, separated with commas, and wrap them in
// brackets.

                v = partial.length === 0 ? '[]' : gap ?
                    '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' :
                    '[' + partial.join(',') + ']';
                gap = mind;
                return v;
            }

// If the replacer is an array, use it to select the members to be stringified.

            if (rep && typeof rep === 'object') {
                length = rep.length;
                for (i = 0; i < length; i += 1) {
                    if (typeof rep[i] === 'string') {
                        k = rep[i];
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            } else {

// Otherwise, iterate through all of the keys in the object.

                for (k in value) {
                    if (Object.prototype.hasOwnProperty.call(value, k)) {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            }

// Join all of the member texts together, separated with commas,
// and wrap them in braces.

            v = partial.length === 0 ? '{}' : gap ?
                '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' :
                '{' + partial.join(',') + '}';
            gap = mind;
            return v;
        }
    }

// If the JSON object does not yet have a stringify method, give it one.

    if (typeof JSON.stringify !== 'function') {
        JSON.stringify = function (value, replacer, space) {

// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.

            var i;
            gap = '';
            indent = '';

// If the space parameter is a number, make an indent string containing that
// many spaces.

            if (typeof space === 'number') {
                for (i = 0; i < space; i += 1) {
                    indent += ' ';
                }

// If the space parameter is a string, it will be used as the indent string.

            } else if (typeof space === 'string') {
                indent = space;
            }

// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.

            rep = replacer;
            if (replacer && typeof replacer !== 'function' &&
                    (typeof replacer !== 'object' ||
                    typeof replacer.length !== 'number')) {
                throw new Error('JSON.stringify');
            }

// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.

            return str('', {'': value});
        };
    }


// If the JSON object does not yet have a parse method, give it one.

    if (typeof JSON.parse !== 'function') {
        JSON.parse = function (text, reviver) {

// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.

            var j;

            function walk(holder, key) {

// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.

                var k, v, value = holder[key];
                if (value && typeof value === 'object') {
                    for (k in value) {
                        if (Object.prototype.hasOwnProperty.call(value, k)) {
                            v = walk(value, k);
                            if (v !== undefined) {
                                value[k] = v;
                            } else {
                                delete value[k];
                            }
                        }
                    }
                }
                return reviver.call(holder, key, value);
            }


// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.

            text = String(text);
            cx.lastIndex = 0;
            if (cx.test(text)) {
                text = text.replace(cx, function (a) {
                    return '\\u' +
                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
                });
            }

// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.

// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

            if (/^[\],:{}\s]*$/
                    .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
                        .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
                        .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.

                j = eval('(' + text + ')');

// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.

                return typeof reviver === 'function' ?
                    walk({'': j}, '') : j;
            }

// If the text is not JSON parseable, then a SyntaxError is thrown.

            throw new SyntaxError('JSON.parse');
        };
    }
}());

console.log('=============== >  json2.js ');

/*
 * Some utilities
 */
if(!String.prototype.startsWith){
	String.prototype.startsWith = function (str) {
		return !this.indexOf(str);
	};
};
if(!String.prototype.endsWith){
	String.prototype.endsWith = function(suffix) {
		return this.indexOf(suffix, this.length - suffix.length) !== -1;
	};
};

if(!String.prototype.hashCode){
	String.prototype.hashCode = function(){
		var hash = 0;
		if (this.length == 0) return code;
		for (var i = 0; i < this.length; i++) {
			var char = this.charCodeAt(i);
			hash = 31*hash+char;
			hash = hash & hash; 
		}
		return hash;
	};
};
if(!String.prototype.trim){
	String.prototype.trim = function(chaine){
		return chaine.replace(/^\s+|\s+$/g,"");
	} ;
};

function trim(chaine) {
	return chaine.replace(/^\s+|\s+$/g,"");
}

function isNumber(val) {
	var exp = new RegExp("^[+-]?[0-9]*[.]?[0-9]*([eE][+-]?[0-9]+)?$","m"); 
	return exp.test(val);
}

var decimaleRegexp = new RegExp("^[+-]?[0-9]*[.][0-9]*([eE][+-]?[0-9]+)?$","m"); 
var bibcodeRegexp  = new RegExp(/^[12][089]\d{2}[A-Za-z][A-Za-z0-9&][A-Za-z0-9&.]{2}[A-Za-z0-9.][0-9.][0-9.BCRU][0-9.]{2}[A-Za-z0-9.][0-9.]{4}[A-Z:.]$/);		

function formatValue(columnName, value, tdNode) {
	if( columnName.match(/.*datalink.*/i)  ){
	//	var url = 'getdatalink?url=' + escape(value);
		//"DataLinkBrowser.startBrowser("http://obs-he-lm:8888/3XMM/smartdatalink?oid=1160803203386703882"
		//tdNode.html("<a class='dl_datalink' title='Get LinkedData'   href='#' onclick='resultPaneView.fireGetDataLink(\"" + value + "\"); return false;'/></a>");
//		tdNode.html("<a class='dl_datalink' title='Get LinkedData'   href='#' onclick='DataLinkBrowser.startBrowser(\"forwardxmlresource?target=" 
//				+  encodeURIComponent(value) + "\" , \"forwardxmlresource\" ); return false;'/></a>"
				tdNode.html("<a class='dl_datalink' title='Get LinkedData'   href='#' onclick='DataLinkBrowser.startBrowser(\""
						+  value + "\" , \"forwardxmlresource\" ); return false;'/></a>"
				);
			
	} else if( value.startsWith("http://") ||  value.startsWith("https://") ) {
		var titlepath = $('#titlepath').text().split('>');
		getDLView(dataTreeView.treePath.nodekey, columnName, value, tdNode);	
	} else if( value.match(/^((position)|(region)|(polygon))/i) ) {
		tdNode.html("<a title='STC Region (click to expand)' class='dl_stc' href='#'  onclick='Modalinfo.info(\"" + value + "\", \"STC Region\");'></a>");
	} else if( value.startsWith("Array") ) {
		tdNode.html("<a title='Data array(click to expand)' class='dl_dataarray' href='#'  onclick='Modalinfo.info(\"" + value + "\", \"Data Array\");'></a>");
	} else if( decimaleRegexp.test(value)){
		tdNode.html((new Number(value)).toPrecision(8));
	} else if( bibcodeRegexp.test(value)){
		tdNode.html("<a title=\"bibcode\" HREF=\http://cdsads.u-strasbg.fr/cgi-bin/nph-bib_query?" + value + "\" target=blank>" + value + "</A>");
	} else {
		tdNode.html(value);
	}
}

function getDLView(node, columnName, url, tdNode) {
	Processing.hide();
	$.getJSON("getproductinfo", {jsessionid: sessionID, url: url}, function(jsdata) {
		if( Processing.jsonError(jsdata, "Cannot connect data") ) {
			tdNode.html("Error");
		} else {
			var cd=null, ct=null, ce=null;
			var dl_class = 'dl_download';
			var dl_cart_tag  = "<a class='dl_cart' title='Add to cart' href='#' onclick='cartView.fireAddUrl(\"" + node + "\", \"" + url + "\"); return false;'/></a>";

			$.each(jsdata, function(k, v) {
				if( k == 'ContentDisposition')    cd = v;
				else if( k == 'ContentType' )     ct = v;
				else if( k == 'ContentEncoding' ) ce = v;
				else if( k == 'nokey' ) {
					if( v.match('401') != null ) {
						dl_class = 'dl_securedownload';
						dl_cart_tag  = "<a class='dl_securecart' title='Add to cart' href='#' onclick='cartView.fireRestrictedUrl(\"" + node + "\", \"" + url + "\"); return false;'/></a>";
					}
				}
			});
			var isFits = false;
			var isVotable = false;
			var samp_tag = "";
			if( (ct != null && (ct.match(/\.fit/i) || ct.match(/fits/))) ||
				(cd != null && (cd.match(/\.fit/i) || cd.match(/fits/)))	){
				//samp_tag = "<a class='dl_samp'     title='Broadcast to SAMP'   href='#' onclick='WebSamp_mVc.fireSendVoreport(\"" + url + "\"); return false;'/></a>";
				samp_tag = "<a class='dl_samp'     title='Broadcast to SAMP'   href='#' onclick='resultPaneView.fireSendVoreportWithInfo(\"" + url + "\"); return false;'/></a>";
				
				isFits = true;
			}
			else if( (ct != null && (ct.match(/\.xml/i) || ct.match(/\.voty/)|| ct.match(/\.votable/))) ||
					 (cd != null && (cd.match(/\.xml/i) || cd.match(/\.voty/)|| cd.match(/\.votable/)))){
				isVotable = true;
				samp_tag = "<a class='dl_samp'     title='Broadcast to SAMP'   href='#' onclick='WebSamp_mVc.fireSendVoreport(\"" + url + "\"); return false;'/></a>";
			}
			var dl_tag = "";
			/*
			 * Will be downloaded by the browser: no need to open a new tab
			 */
			if( (ce != null && (ce == 'gzip' || ce == 'zip')) || isFits ){
				dl_tag = "<a class='" + dl_class + "' title='Download Data' href='javascript:void(0);' onclick='PageLocation.changeLocation(\"" + url + "\");'></a>";
			} else {
				dl_tag = "<a class='" + dl_class + "' title='Download Data' href='javascript:void(0);' onclick='PageLocation.changeLocation(\"" + url + "\");' target=blank></a>";
			}

			tdNode.html(
				  "<a class='dl_info' title='Get info about' href='#' onclick='resultPaneView.fireGetProductInfo(\"" + url + "\"); return false;'></a>"
				+ dl_tag 
			    + dl_cart_tag
			    + samp_tag );
		}
	});
}
console.log('=============== >  formator.js ');

jQuery.extend({

	ResultPaneModel: function(){
		/**
		 * keep a reference to ourselves
		 */
		var that = this;
		/**
		 * who is listening to us?
		 */
		var listeners = new Array();
		var dataJSONObject;
		/**
		 * Query displayed data are coming from
		 */
		var current_query = "";

		/**
		 * add a listener to this view
		 */
		this.addListener = function(list){
			listeners.push(list);
		};

		this.processShowRecord= function(oid){
			var jsdata ="";
			Processing.show("Get object detail");
			$.getJSON("getobject", {jsessionid: sessionID, oid: oid }, function(data) {
				Processing.hide();
				if( Processing.jsonError(data, "") ) {
					return;
				}

				else {
					jsdata = data;
					that.notifyDetailLoaded(oid, jsdata);
				}
			});
		};

		this.processShowMeta= function(){
			Processing.show("Get table description");
			$.getJSON("gettable", {jsessionid: sessionID, node: "aharray", name:tp }, function(data) {
				Processing.hide();
				if( Processing.jsonError(data, "get metadata") ) {
					return;
				}
				else {
					that.notifyMetaLoaded(data);
				}
			});
		};

		this.processShowMetaNode= function(treepath){
			Processing.show("Get table description");
			$.getJSON("gettable", {jsessionid: sessionID, node: treepath.nodekey, schema: treepath.schema, table:treepath.tableorg }, function(data) {
				Processing.hide();
				if( Processing.jsonError(data, "get attribute handlers") ) {
					return;
				}
				else {
					that.notifyMetaLoaded(data);
				}
			});
		};


		this.processShowSimbad= function(coord){
			window.open("simbad?coord=" + escape(coord), "Simbad");
		};

		
		this.downloadVOTable = function() {
			/*
			 * Job ids is detected by analysing the title
			 */
			var titlepath = $('#titlepath').text().split('>');
			if( titlepath.length == 4  ) {
				tapView.fireDownloadVotable(titlepath[0], titlepath[3].replace('job ', ''));
			}
			else {
				Modalinfo.info("Cannot identify the current JOB", 'Error');	
			}
		};
		this.downloadFITS = function() {
			var url = "getqueryreport?query=" + escape(current_query) + "&protocol=auto&format=fits";
			PageLocation.changeLocation(url);
		};
		this.downloadZip = function() {
			/*
			 * The mode (TAP/SaadaQL is detected by analysing the title
			 */
			var titlepath = $('#titlepath').html().split('&gt;');
			if( titlepath.length == 3 && titlepath[1] == 'Job' ) {
				Modalinfo.info("Not implemented for TAP queries", 'Info');
			} else {
				var url = "getqueryreport?query=" + escape(current_query) + "&protocol=noprotocol&format=zipball";
				PageLocation.changeLocation(url);
			}
		};
		this.sampBroadcast = function() {
			/*
			 * Job ids is detected by analysing the title
			 */
			var titlepath = $('#titlepath').text().split('>');
			if( titlepath.length == 4  ) {
					tapView.fireDownloadVotable(titlepath[0], titlepath[3].replace('job ', ''));
			}
			else {
				Modalinfo.info("Cannot identify the current JOB", 'Error');	
			}

//			var url = "getqueryreport?query=" + escape(current_query) + "&protocol=auto&format=fits";
//			window.open(url, 'DL VOTable');
		};

		/*
		 * Listener notifications
		 */
		this.notifyJobInProgress = function(){
			$.each(listeners, function(i){
				listeners[i].jobInProgress();
			});
		};
		this.notifyJobDone = function(dataJSONObject){
			$.each(listeners, function(i){
				listeners[i].jobIsDone(dataJSONObject);
			});
		};
		this.notifyTableInitDone = function(dataJSONObject, query){
			$.each(listeners, function(i){
				listeners[i].tableIsInit(dataJSONObject, query);
			});
		};
		this.notifyJobFailed = function(textStatus){
			$.each(listeners, function(i){
				listeners[i].jobFailed(textStatus);
			});
		};
		this.notifyDetailLoaded= function(oid, jsdata){
			$.each(listeners, function(i){
				listeners[i].detailIsLoaded(oid, jsdata);
			});
		};
		this.notifyMetaLoaded= function(jsdata){
			$.each(listeners, function(i){
				listeners[i].metaIsLoaded(jsdata);
			});
		};
		this.notifyCounterpartsLoaded= function(jsdata){
			$.each(listeners, function(i){
				listeners[i].counterpartsAreLoaded(jsdata);
			});
		};
	}
});

console.log('=============== >  resultPaneModel.js ');

jQuery.extend({

	ResultPaneView : function() {
		/**
		 * keep a reference to ourselves
		 */
		var that = this;
		/**
		 * who is listening to us?
		 */
		var listeners = new Array();

		/**
		 * add a listener to this view
		 */
		this.addListener = function(list) {
			listeners.push(list);
		};

		this.fireGetProductInfo = function(url) {
			Processing.show("Waiting on product info");

			$.getJSON("getproductinfo", {jsessionid: sessionID, url: url}, function(jsdata) {
				Processing.hide();
				if( Processing.jsonError(jsdata, "Cannot get product info") ) {
					return;
				}
				else {
					retour = "url: " + url + "\n";
					$.each(jsdata, function(k, v) {
						retour += k + ": " + v  + "\n";
					});
					Modalinfo.info(retour, "Product Info");
				}
			});
		}	;	
		this.fireSendVoreportWithInfo = function (url){
			Processing.show("Waiting on product info");

			$.getJSON("getproductinfo", {jsessionid: sessionID, url: url}, function(jsdata) {
				Processing.hide();
				if( Processing.jsonError(jsdata, "Cannot get product info") ) {
					return;
				}
				else {
					var mtype=null, name=null;
					$.each(jsdata, function(k, v) {
						if( k.match(/contenttype/i) ) {
							if( v.match(/fits$/) ) {
								mtype = "table.load.fits";
							} else {
								mtype = "table.load.votable";
							}
						} else if( k.match(/contentdisposition/i) ) {
							var regex = new RegExp(/filename=(.*)$/) ;
							var results = regex.exec(v);
							if(results){
								name = results[1];
							}
						}
					});
					WebSamp_mVc.fireSendVoreport(url, mtype, name);
				}
			});
		}	;	

		this.fireGetDataLink = function(url) {
			Processing.show("Waiting on product info");

			$.getJSON("getdatalink", {jsessionid: sessionID, url: url}, function(jsdata) {
				Processing.hide();
				if( Processing.jsonError(jsdata, "Cannot get datalink") ) {
					return;
				}
				else {
					var table = '';
					var title = "Data link provided by <i>"
						+ url 
						+ "</i>";
					table += '<h2><img src="images/Relation.png"><span>' + title
					+ '</span></h2>';
					table += "<h4 id=\"mappedmeta\" class='detailhead'> <img src=\"images/tdown.png\"> Links </h4>";
					table += "<div class='detaildata'>";
					table += "<table width=99% cellpadding=\"0\" cellspacing=\"0\" border=\"0\"  id=\"detailtable\" class=\"display\"></table>";
					table += "</div>";


					if ($('#detaildiv').length == 0) {
						$(document.documentElement).append(
						"<div id=detaildiv style='width: 99%; display: none;'></div>");
					}
					$('#detaildiv').html(table);
					$('#detailtable').dataTable(
							{
								"aoColumns" : jsdata.columns,
								"aaData" : jsdata.data,
								//	"sDom" : '<"top"f>rt<"bottom">',
								"bPaginate" : false,
								"aaSorting" : [],
								"bSort" : false,
								"bFilter" : true,
								"bAutoWidth" : true,
								"fnRowCallback": function( nRow, aData, iDisplayIndex ) {
									for( var c=0 ; c<aData.length ; c++ ) {
										formatValue(this.fnSettings().aoColumns[c].sTitle, aData[c], $('td:eq(' + c + ')', nRow));
									}
									return nRow;
								}

							});

					$('#detaildiv').modal();

				}
			});
		}	;	
		this.fireDownloadProduct = function(url) {
			Processing.show("Waiting on product info");

			$.getJSON("getproductinfo", {jsessionid: sessionID, url: url}, function(jsdata) {
				Processing.hide();
				if( jsdata == undefined || jsdata == null ) {
					window.open(url);
					return;
				}
				else {
					var ct, ce;
					$.each(jsdata, function(k, v) {
						if( k == 'ContentDisposition')    fn = v;
						else if( k == 'ContentType' )     ct = v;
						else if( k == 'ContentEncoding' ) ce = v;
					});
					/*
					 * Will be downloaded by the browser: no need to open a new tab
					 */
					if( (ce != null && (ce == 'gzip' || ce == 'zip')) ||
							(ct != null && (ct.match(/\.fit/i) || ct.match(/fits/))) ){
						document.location = url;
					} else {
						window.open(url);
					}

				}
			});

		};

		this.fireSubmitQueryEvent = function() {
			$("#resultpane").html();
			tapView.fireSubmitQueryEvent();
		};
		this.fireHisto = function(direction) {
		};
		this.fireStoreHisto = function(query) {
		};

		this.fireDownloadVOTable = function(query) {
			$.each(listeners, function(i) {
				listeners[i].controlDownloadVOTable();
			});
		};
		this.fireDownloadFITS = function(query) {
			$.each(listeners, function(i) {
				listeners[i].controlDownloadFITS();
			});
		};
		this.fireDownloadCart = function(query) {
			$.each(listeners, function(i) {
				listeners[i].controlDownloadCart();
			});
		};
		this.fireSampBroadcast = function(query) {
			$.each(listeners, function(i) {
				listeners[i].controlSampBroadcast();
			});
		};
		this.fireShowRecord = function(oid) {
			$.each(listeners, function(i) {
				listeners[i].controlShowRecord(oid);
			});
		};
		this.fireShowMeta = function() {
			$.each(listeners, function(i) {
				listeners[i].controlShowMeta();
			});
		};
		this.fireShowMetaNode = function(treepath) {
			$.each(listeners, function(i) {
				listeners[i].controlShowMetaNode(treepath);
			});
		};
		this.fireShowSources = function(oid) {
			$('#saadaqllang').attr('checked', 'checked');
			$('#taptab').hide();
			$('#saptab').hide();
			$('#saadaqltab').show('slow');
			$("#qhistocount").css("visibility", "visible");
			saadaqlView.fireDisplayHisto();

			$.each(listeners, function(i) {
				listeners[i].controlShowSources(oid);
			});
		};
		this.fireShowSimbad = function(coord) {
			$.each(listeners, function(i) {
				listeners[i].controlShowSimbad(coord);
			});
		};
		this.fireShowVignette = function(oid, title) {
			ModalInfo.info('Preview of ' + title,
					"<img class=vignette src='getvignette?oid=" + oid
					+ "'>");
		};
		this.fireShowPreview = function(preview_url, title) {
			ModalInfo.info('Preview of ' + title,
					"<img class=vignette src='" + preview_url + "'>");
		};

		this.fireSwitchForm= function() {
			var height = $(window).height() ;
			var icon = $('#formexpender').css("background-image");
			if( icon.match("screen_up") != null ) {
				$('#formexpender').css("background-image", "url(images/screen_down.png)");
				$('#formexpender').attr("title", "Expend query form");
				height='10%';
			}
			else {
				$('#formexpender').css("background-image", "url(images/screen_up.png)");
				$('#formexpender').attr("title", "Minimize query form");
				height='90%';
			}
			layoutPane.sizePane("south", height);
			//	$("div#accesspane").trigger("resize",[ height]);		
		};
		this.fireExpandForm= function() {
			var height = $(window).height() ;
			var icon = $('#formexpender').css("background-image");
			if( icon.match("screen_up") == null ) {
				$('#formexpender').css("background-image", "url(images/screen_up.png)");
				$('#formexpender').attr("title", "Minimize query form");
				height='90%';
				layoutPane.sizePane("south", height);
			}
			//	$("div#accesspane").trigger("resize",[ height]);		
		};
		
		this.fireRemoveAllJobs= function() {
			Modalinfo.confirm("Do you really want to remove all jobs?"
					, function(){$("#tapjobs a").click();}
					, "Removing Jobs");
		};
		this.clearTapResult = function() {
			$("#resultpane").html("");
		};
		this.showProgressStatus = function() {
			Modalinfo.info("Job in progress", 'Info');
		};
		this.showFailure = function(textStatus) {
			Modalinfo.info("view: " + textStatus, 'Failutr');
		};

		this.showMeta = function(jsdata) {
			if (jsdata.errormsg != null) {
				Modalinfo.info("FATAL ERROR: Cannot show object detail: "
						+ jsdata.errormsg, 'Server Error');
				return;
			}

			var table = '';
			var histo = '<img src="images/question.png">';


			var title = "Columns of table <i>"
				+ jsdata.table
				+ "</i> of node <i>"
				+ jsdata.nodekey 
				+ "</i>";
			table += '<h2> ' + histo + ' META <span>' + title
			+ '</span></h2>';
			table += "<h4 id=\"mappedmeta\" class='detailhead'> <img src=\"images/tdown.png\"> Table Columns </h4>";
			table = "<div class='detaildata'>";
			table += "<table width=99% cellpadding=\"0\" cellspacing=\"0\" border=\"0\"  id=\"detailtable\" class=\"display\"></table>";
			table += "</div>";


			if ($('#detaildiv').length == 0) {
				$(document.documentElement).append(
				"<div id=detaildiv style='width: 99%; display: none;'></div>");
			}
//			$('#detaildiv').html(table);
			Modalinfo.dataPanel(title, table, null);
			$('#detailtable').dataTable(
					{
						"aoColumns" : jsdata.attributes.aoColumns,
						"aaData" : jsdata.attributes.aaData,
						//	"sDom" : '<"top"f>rt<"bottom">',
						"bPaginate" : false,
						"aaSorting" : [],
						"bSort" : false,
						"bFilter" : true,
						"bAutoWidth" : true
					});
//			$('#detaildiv').modal(title, table, null);

		};

		this.showTapResult = function(treepath, jid, jsdata, attributeHandlers) {
			var table = "<table cellpadding=\"0\" cellspacing=\"0\" border=\"1\"  id=\"datatable\" class=\"display\"></table>";
			$("#resultpane").html(table);
//			var nb_cols = jsdata.aoColumns.length;
//			for( var r=0 ; r<jsdata.aaData.length ; r++) {
//				var line = jsdata.aaData[r];
//				for( var l=0 ; l<nb_cols ; l++) {
//					var num = line[l];
//					//line[l] = formatValue(jsdata.aoColumns[l].sTitle, num);
//				}
//			}
			attributeHandlers = tapConstraintEditor.getAttributeHandlers();
			var aoColumns = new Array();
			for(var i=0 ; i<jsdata.aoColumns.length ; i++) {
				var title ;
				if( attributeHandlers == undefined ) {
					title = "No descritption available"
						+ " - This job has likely been initiated in a previous session" ;
				}
				else {
					var ah = attributeHandlers[jsdata.aoColumns[i].sTitle];/*
					/*
					 * Column name could be published in upper case but returned by the DBMS in lower case.
					 */
					if(ah == undefined  ) {
						ah = attributeHandlers[jsdata.aoColumns[i].sTitle.toLowerCase()];
					}
					if(ah == undefined  ) {
						ah = attributeHandlers[jsdata.aoColumns[i].sTitle.toUpperCase()];
					}
					if( ah == undefined ) {
						title = "No description available (joined query?)";
					} else {
						title = ah.description
						+ " - Name: " + ah.nameorg
						+ " - Unit: " + ah.unit
						+ " - UCD: " + ah.ucd
						+ " - UType: " + ah.utype
						+ " - DataType: " + ah.dataType;
					}
				}
				aoColumns[i] = {sTitle: '<span title="' + title + '">' + jsdata.aoColumns[i].sTitle + '</span>'};
			}

			$('#datatable').dataTable({
				"aLengthMenu": [5, 10, 25, 50, 100],
				"aoColumns" : aoColumns,
				"aaData" : jsdata.aaData,
				//"sDom" : '<"top"f>rt',
				"bPaginate" : true,
				"aaSorting" : [],
				"bSort" : false,
				"bFilter" : true,
				"fnRowCallback": function( nRow, aData, iDisplayIndex ) {
					for( var c=0 ; c<aData.length ; c++ ) {
						formatValue(this.fnSettings().aoColumns[c].sTitle, aData[c], $('td:eq(' + c + ')', nRow));
					}
					return nRow;
				}
			} );

			$('#datatable span').tooltip( { 
				track: true, 
				delay: 0, 
				showURL: false, 
				opacity: 1, 
				fixPNG: true, 
				showBody: " - ", 
				// extraClass: "pretty fancy", 
				top: -15, 
				left: 5 	
			});	
		};;

		this.displayResult = function(dataJSONObject) {
		};

		this.initTable = function(dataJSONObject, query) {
			if( Processing.jsonError(dataJSONObject, "") ) {
				return;
			}
			else {
				/*
				 * Get table columns
				 */
				var ahs = dataJSONObject["attributes"];
				var table = "<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\"  id=\"datatable\" class=\"display\">"
					+ "<thead>" + "<tr>";
				for (i = 0; i < ahs.length; i++) {
					table += "<th>" + ahs[i].name + "</th>";
				}
				/*
				 * Build empty table
				 */
				table += "</tr>"
					+ "</thead>"
					+ "<tbody>"
					+ "<tr><td colspan="
					+ i
					+ " class=\"dataTables_empty\">Loading data from server</td></tr>"
					+ "</tbody>" + "</table>";
				$("#resultpane").html(table);
				/*
				 * Connect the table with the DB
				 */
				$('#datatable').dataTable({
					"bServerSide" : true,
					"bProcessing" : true,
					"aaSorting" : [],
					"bSort" : false,
					"bFilter" : false,
					"sAjaxSource" : "nextpage?jsessionid=" + sessionID
				});
			}
		};
	}
});

console.log('=============== >  resultPaneView.js ');

jQuery.extend({

	ResultPaneController: function(model, view){
		/**
		 * listen to the view
		 */
		var vlist = {
				controlShowRecord: function(oid){
					model.processShowRecord(oid);
				},
				controlShowMeta: function(){
					model.processShowMeta();
				},
				controlShowMetaNode: function(treepath){
					model.processShowMetaNode(treepath);
				},
				controlDownloadVOTable: function(){
					model.downloadVOTable();				
				},
				controlDownloadFITS: function(){
					model.downloadFITS();				
				},
				controlDownloadZip: function(){
					model.downloadZip();				
				},
				controlSampBroadcast: function(){
					model.sampBroadcast();				
				}

		}
		view.addListener(vlist);

		var mlist = {
				jobInProgress : function(){
					view.showProgressStatus();
				},
				jobFailed : function(textStatus){
					view.showFailure(textStatus);
				},
				jobIsDone : function(dataJSONObject){
					view.displayResult(dataJSONObject);
				},
				tableIsInit : function(dataJSONObject, query){
					view.initTable(dataJSONObject, query);
				},
				detailIsLoaded: function(oid, dataJSONObject){
					view.showDetail(oid, dataJSONObject);
				},
				metaIsLoaded: function(dataJSONObject){
					view.showMeta(dataJSONObject);
				},
				counterpartsAreLoaded: function(dataJSONObject){
					view.showCounterparts(dataJSONObject);
				}
		}

		model.addListener(mlist);
	}
});

console.log('=============== >  resultPaneControler.js ');

jQuery.extend({

	NodeFilterModel: function(){
		/**
		 * who is listening to us?
		 */
		var listeners = new Array();
		/**
		 * add a listener to this view
		 */
		this.addListener = function(list){
			listeners.push(list);
		};
		this.getFilteredNodes  = function(node){
			var tr = Array();
			/*
			 * Build a list with all selected tables
			 * If all table are selected, take "any" as parameter
			 */
			var any = true;
			$("#nodeFilterList span").each(function() {
				if( $(this).parent().attr('class') == 'tableNotSelected') {
					any = false;
				}
			});
			if( ! any ){
				$("#nodeFilterList span").each(function() {
					if( $(this).parent().attr('class') == 'tableSelected') {
						tr[tr.length ] = $(this).text();
					}
				});
			} else {
				tr.push("any");
			}
			/*
			 * Ask for the new table list
			 */
			Processing.show("Waiting on " + node + " filtered node description");
			$.getJSON("getnode", {jsessionid: sessionID, node: node , filter: $("#nodeFilter").val(), selected:tr.join(',') }, function(jsdata) {
				Processing.hide();
				if( Processing.jsonError(jsdata, "Cannot make data tree") ) {
					return;
				} else {
					dataTreeView.fireBuildTree(jsdata);
					$.modal.close();
				}
			});
		};
	}
});

console.log('=============== >  nodeFilterModel.js ');

jQuery.extend({

	NodeFilterView : 
		/**
	 * 
	 */
		function() {
		/**
		 * keep a reference to ourselves
		 */
		var that = this;
		/**
		 * who is listening to us?
		 */
		var listeners = new Array();

		/**
		 * add a listener to this view
		 */
		this.addListener = function(list) {
			listeners.push(list);
		};

		this.fireGetFilteredNodes  = function(node){
			$.each(listeners, function(i) {
				listeners[i].controlGetFilteredNodes(node);
			});
		};
		/**
		 * Open a modal box allowing to select the tables of the node which must be displayed in the tree
		 * @param node
		 */
		this.fireOpenSelectorWindow = function(node) {

			var table = '<h2><span>Table Selector for node  <i>' + node + '</i></span></h2>'
			+ "<div class='detaildata'>"
			+ "    <div class='detaildata' style='width: 60%; height: 60px;display: inline;float:left;overflow: hidden;'>"
			+ "        <span class=help>Give a filter on catalogue name or description:"
			+ "        <br> - The filter is a RegExp case unsensitive."
			+ "        <br> - Type [RETURN] to apply"
			+ "        </span>"
			+ "    </div>"
			+ "    <div class='detaildata' style='width: 39%; height: 45px;display: inline;float:right; padding-top:15px;'>"
			+ "        <input id=nodeFilter type=texte width=24 style='background-color: whitesmoke;'>"
			+ "    </div>"
			+ "    <hr><p class=help>The number of selected tables returned by the server is limited to 100 in any case.<p>"
			+ "    <div id=nodeFilterList class='detaildata' style='border: 1px black solid; background-color: whitesmoke; width: 90%; height: 380px; overflow: auto;margin : auto;position:relative'></div>"
			+ "    <p class=help>Unselect the tables you not want to access"
			+ "    (<a href='#' onclick=\"$('#nodeFilterList input').attr('checked', 'true');$('#nodeFilterList li').attr('class', 'tableSelected');\">select</a> /"
			+ "     <a href='#' onclick=\"$('#nodeFilterList input').removeAttr('checked');$('#nodeFilterList li').attr('class', 'tableNotSelected');\">unselect</a> all)<br>"
			+ "    Caution: You cannot refine your selection once it is accepted (Version 1.1)<p><hr>"
			+ "    <input type=button value='accept' onclick='nodeFilterView.fireGetFilteredNodes(\"" + node + "\");' style='font-weight: bold;'>"
			+ "    <span class=help>(Type [ESC] to close the window)</span>"
			+ "    </div>"
			+ "</div>";

			if ($('#detaildiv').length == 0) {
				$(document.documentElement).append(
				"<div id=detaildiv style='width: 99%; display: none;'></div><hr>");
			}
			$('#detaildiv').html(table);

			$('#detaildiv').modal( { onShow: function(dlg) {
				$(dlg.container).css('height','auto').css('width','500px');
				}
			});
			$("#simplemodal-container").css('height', 'auto'); 
			$("#simplemodal-container").css('width', 'auto'); 
			$(window).trigger('resize.simplemodal'); 
			//Processing.show("Filering meta data");
			$("#nodeFilter").keyup(function(event) {
				if(event.keyCode != 13) {	            
					that.applyFilter(node);
				}
			});
			this.applyFilter(node);
		};	
		
		this.applyFilter = function(node) {
			//Processing.show('Get filtered table list');			
			$.getJSON("getnode", {jsessionid: sessionID, node: node, filter: $("#nodeFilter").val(), selected: ''}, function(jsdata) {
				//Processing.hide();
				if( Processing.jsonError(jsdata, "Cannot get the node selection") ) {
					return;
				} else {
					that.fireShowNodeSelection($("#nodeFilterList"), jsdata);
				}
			});
			
		};
		/**
		 * Display in the div the list of selected tables returned by the server 
		 */
		this.fireShowNodeSelection = function(listDiv, jsSelection)  {
			listDiv.html('');		
			for( var i=0 ; i<jsSelection.schemas.length ; i++ ) {
				var schema = jsSelection.schemas[i];
				var sn = schema.name;
				if( sn == "TAP_SCHEMA" || sn == 'tap_schema' ) {
					continue;
				}
				listDiv.append("<span style='float: left; width: 100%; background-color: white; border: 1px solid black'><b>Schema</b> " + sn + "</span>");		
				var list = "<ul class=attlist>";
				for( var j=0 ; j<schema.tables.length ; j++ ) {
					var table = schema.tables[j];
					list += "<li class=tableSelected >" 
						+ "<input  type='checkbox' checked onclick='nodeFilterView.fireSelectFilteredNode($(this));'>"
						+ "<span style='font-color: black;'>" + table.name + "</span>"
					    + " <i>" + table.description + "</i>"
					    + "</li>";
				}
				list += "</ul>";
				listDiv.append(list);		
			}
		};
				
		/**
		 * Select unselect one filter table
		 * 
		 */
		this.fireSelectFilteredNode = function(button) {
			if( button.attr('checked') ) {
				button.parent().attr('class', 'tableSelected');
			} else {
				button.parent().attr('class', 'tableNotSelected');
			}
		};

	}
});

console.log('=============== >  nodeFilterView.js ');

jQuery.extend({

	NodeFilterController: function(model, view){
		/**
		 * listen to the view
		 */
		var vlist = {
				controlGetFilteredNodes: function(node){
					model.getFilteredNodes(node);
				}
		};
		view.addListener(vlist);

		var mlist = {
		};

		model.addListener(mlist);
	}
});

console.log('=============== >  nodeFilterControler.js ');

jQuery.extend({

	TapModel: 

		/**
		 * @param pmodel
		 */
		function(pmodel){
		/**
		 * keep a reference to ourselves
		 */
		var that = this;
		/**
		 * who is listening to us?
		 */
		var listeners = new Array();
		/*
		 * What we have to store and play with
		 * the values of the following fields are attached to the current selected node.
		 */
		var attributesHandlers = new Array();

		var jobs = new Array();

		/**
		 * add a listener to this view
		 */
		this.addListener = function(list){
			listeners.push(list);
		};


		this.submitQuery = function(){
			if( dataTreeView.treePath == null) {
				Modalinfo.error("No data node seelcted: canot process any query\nSelect the data table table you want to query in the 'Tap Nodes' panel\nand ClickClick on it");
				return;
			}
			Processing.show("Run job");
			Out.info(' start ' + $('#saadaworkingContent').css('display'));
			var limit = getQLimit();
			$.ajax({type: 'POST'
				, url:"runasyncjob"
					, dataType: 'json'
						, data: {jsessionid: sessionID
							, NODE: dataTreeView.treePath.nodekey
							, TREEPATH: dataTreeView.treePath.nodekey + ";" + dataTreeView.treePath.schema + ";" + dataTreeView.treePath.table
							, REQUEST: "doQuery"
								, LANG: 'ADQL'
									, FORMAT: 'json'
										, PHASE: 'RUN'
											, MAXREC: limit
											, UPLOAD:tapConstraintEditor.getUploadedFile()
											, QUERY: adqlQueryView.getQuery() }
			, beforeSend: function(  jqXHR, settins) {
				Out.info('before ' + $('#saadaworkingContent').css('display'));
			}
			, error: function(  jqXHR,  textStatus,  errorThrown) {
				Processing.hide();
				Modalinfo.error(errorThrown);
			}
			, success: function(jsondata) {
				Processing.hide();
				if( Processing.jsonError(jsondata, "tap/async Cannot get job status") ) {
					return;
				} else {
					Processing.show("Run job " +  jsondata.status.job.jobId);
					var jobParam = {"treepath" : jQuery.extend({}, dataTreeView.treePath), "status": jsondata.status, "session": jsondata.session};
					jobParam.treepath.jobid = jsondata.status.job.jobId;
					
					jv = new $.JobView();
					jm = new $.JobModel(jobParam);
					new $.JobControler(jm, jv);
					jobs[jsondata.status.job.jobId] = jv;
					jv.fireInitForm('tapjobs', attributesHandlers);
					ViewState.fireSubmitted(dataTreeView.treePath,jsondata.status.job.jobId  );
					jv.fireCheckJobCompleted();
				}
			}
			});
		};

		this.selectJob = function( id) {
			for( var i in jobs) {
				jobs[i].setSelected(false);
			}
			jobs[id].setSelected(true);
		};
		
		this.removeJob = function(id) {
			delete jobs[id];
		};

		/**
		 * Just called at init time to display the job still stored in the session (not working with sessionID in URLs)
		 */
		this.refreshJobList= function() {
			Processing.show("Refresh job list");
			$.getJSON("joblist", {jsessionid: sessionID, FORMAT: "json"}, function(jsondata) {
				Processing.hide();
				if( Processing.jsonError(jsondata, "Cannot get jobs list") ) {
					return;
				}
				Processing.show("Update Job Status");
				for( var i=0 ; i<jsondata.length ; i++) {
					var job = jsondata[i];
					jv  = new $.JobView(job.jobid);
					jm = new $.JobModel(job.treepath, job.status.job, job.session);

					new $.JobControler(jm, jv);						
					jv.fireInitForm('tapjobs');
					if( jv.fireGetPhase() == 'EXECUTING' ) {
						pendingJobs[job.jobid] = jv;
					}
					listTimer = setTimeout("tapView.fireUpdateRunningJobList();", 5000);	
				}
				Processing.hide();
			});		
		};
		this.displayResult = function(jdataTreePath) {
			jobs[jdataTreePath.jobid].fireJobAction();
			this.selectJob(jdataTreePath.jobid);				
		};
		
		/*
		 * Listener notifications
		 */
		this.notifyInitDone = function(){
			$.each(listeners, function(i){
				listeners[i].isInit(attributesHandlers, attributesHandlers);
			});
		};
		this.notifyCoordDone = function(key, constr){
			$.each(listeners, function(i){
				listeners[i].coordDone(key, constr);
			});
		};
		this.notifyQueryUpdated= function(query) {
			$.each(listeners, function(i){
				listeners[i].queryUpdated(query);
			});
		};
		this.notifyNewJobs= function() {
			lastJob.controlInitForm();
		};
		this.notifyTableChanged= function() {
			$.each(listeners, function(i){
				listeners[i].tableChanged(attributesHandlers, attributesHandlers);
			});
		};
	}
});

console.log('=============== >  tapModel.js ');

jQuery.extend({

	TapView: function(){
		/**
		 * keep a reference to ourselves
		 */
		var that = this;
		/**
		 * who is listening to us?
		 */
		var listeners = new Array();
		/**
		 * add a listener to this view
		 */
		this.addListener = function(list){
			listeners.push(list);
		};
		/*
		 * Fire external events
		 */
		this.fireTreeNodeEvent = function(treepath, andsubmit){
			adqlQueryView.fireSetTreePath(treepath);
			adqlQueryView.fireAddConstraint("tap", "limit", [getQLimit()]);
			// set to generate errors
			///adqlQueryView.fireAddConstraint("tap", "limit", ['sfsfsfsdfds']);
			tapConstraintEditor.fireSetTreepath(treepath);
			tapColumnSelector.fireSetTreepath(treepath, ((andsubmit)? this.fireSubmitQueryEvent: null));

		};
		this.fireSubmitQueryEvent = function(){
			$.each(listeners, function(i){
				listeners[i].controlSubmitQueryEvent();
			});
		};
		/**
		 * Just called at init time to display the job still stored in the session (not implemented yet)
		 */
		this.fireRefreshJobList = function(){
			$('#tapjobs').html('');
			$.each(listeners, function(i){
				listeners[i].controlRefreshJobList();
			});
		};
		this.fireSelectJob= function(id) {			
			$.each(listeners, function(i){
				listeners[i].controlSelectJob(id);
			});
		};
		this.fireRemoveJob = function(id) {
			$.each(listeners, function(i){
				listeners[i].controlRemoveJob(id);
			});		
		};

		this.fireDisplayResult= function(jdataTreePath){
			$.each(listeners, function(i){
				listeners[i].controlDisplayResult(jdataTreePath);
			});
		};
		this.fireFilterColumns = function(val) {
			$('.kw_list').find('span').each(
					function(){

						var attr = ($(this).text().split("("))[0];
						if( val == '' || attr.indexOf(val) != -1 ) {
							$(this).parent().show();
						}
						else {
							$(this).parent().hide();							
						}
					});
		};
		/*
		 * Local processing
		 */

		this.showProgressStatus = function(){
			Modalinfo.info("Job in progress", 'Info');
		};
		this.showFailure = function(textStatus){
			Modalinfo.info("view: " + textStatus, 'Info');
		}	;	
		this.initForm= function(attributesHandlers, selectAttributesHandlers){
			/*
			 * Reset form
			 */
			//$('#adqltext').val('');
			$('#tapconstraintlist').html('');
			$('#kwalpha').html('');
			$('#kwdelta').html('');
			$('#attlist').html('');
			$('#tapselectlist').html('');
			$('#taporderby').html('');
			$('#tapselectmeta').html('');
			$('.kw_filter').val('');
			that.setNewTable(attributesHandlers, selectAttributesHandlers);
			$("#taptab").tabs({
				selected: 2
			});
		};

		this.setNewTable= function(attributesHandlers, selectAttributesHandlers){
			/*
			 * Get table columns for where clause
			 */
			var table  = "<ul class=attlist>";
			for( i in attributesHandlers  ) {
				var ah = attributesHandlers[i];
				var title = ah.description
				+ " - Name: " + ah.name
				+ " - Unit: " + ah.unit
				+ " - UCD: " + ah.ucd
				+ " - UType: " + ah.utype
				+ " - DataType: " + ah.dataType;
				
				table += "<li class=\"ui-state-default\"><span class=item title='" + title + "'>" 
					+ ah.name
					+ " (" + ah.dataType 
					+ ") " + ah.unit 
					+ "</span></li>";
			}
			table += "</select>";
			$("#tapmeta").html(table);
			$('#tapmeta span').tooltip( { 
				track: true, 
				delay: 0, 
				showURL: false, 
				opacity: 1, 
				fixPNG: true, 
				showBody: " - ", 
				// extraClass: "pretty fancy", 
				top: -15, 
				left: 5 	
			});	

			$(function() {
//				$("#tapmeta" ).sortable({
//					revert: "true"
//				});
				$( "div#tapmeta li" ).draggable({
					connectToSortable: "#tapconstraintslist",
					helper: "clone",
					revert: "invalid"
				});

			});

			/*
			 * Get table columns for select clause
			 */
			var table  = "<ul class=attlist>";
			for( i in selectAttributesHandlers  ) {
				table += "<li class=\"ui-state-default\"><span class=item>" 
					+ selectAttributesHandlers[i].name
					+ " (" + selectAttributesHandlers[i].dataType 
					+ ") " + selectAttributesHandlers[i].unit 
					+ "</span></li>";
			}
			table += "</select>";
			$("#tapselectmeta").html(table);
			$(function() {
//				$("#tapselectmeta" ).sortable({
//					revert: "true"
//				});
				$( "div#tapselectmeta li" ).draggable({
					connectToSortable: "#tapselectlist",
					helper: "clone",
					revert: "invalid"
				});

			});
			
		};
		this.coordDone= function(key, constr){
			$('#CoordList').append("<div id=" + key + "></div>");

			$('#' + key).html('<span id=' + key + '_name>' + constr + '</span>');
			$('#' + key).append('<a id=' + key + '_close href="javascript:void(0);" class=closekw></a>');
			$('#' +  key + "_close").click(function() {
				$('#' +  key).remove();
				that.fireUpdateQueryEvent();
			});
		};

		this.queryUpdated= function(query){
			$('#adqltext').val(query);
		};

		this.jobView= function(jobcontroler){
			jobcontroler.fireInitForm('tapjobs');
		};

		this.fireDisplayHisto = function(){
			var result = '';
			result += '<img src="images/histoleft-grey.png">';
			result += '<img src="images/historight-grey.png">';	
			$('#histoarrows').html('');
			$('#histoarrows').html(result);
		};
	}
});
console.log('=============== >  tapView.js ');

jQuery.extend({

	TapController: function(model, view){
		/**
		 * listen to the view
		 */
		var vlist = {
				controlSubmitQueryEvent: function(){
					model.submitQuery();
				},
				controlRefreshJobList: function(){
					model.refreshJobList();
				},
				controlSelectJob: function(id) {
					model.selectJob(id);
				},
				controlRemoveJob: function(id) {
					model.removeJob(id);
				},
				controlDisplayResult: function(jdataTreePath){
					model.displayResult(jdataTreePath);
				}
		};
		view.addListener(vlist);

		var mlist = {
				isInit : function(attributesHandlers, selectAttributesHandlers){
					view.initForm(attributesHandlers, selectAttributesHandlers);
				},
				tableChanged : function(attributesHandlers, selectAttributesHandlers){
					view.setNewTable(attributesHandlers, selectAttributesHandlers);
				},
				coordDone : function(key, constr){
					view.coordDone(key, constr);
				},
				queryUpdated : function(query){
					view.queryUpdated(query);
				},
				newJob: function(jobcontroleur){
					view.jobView(jobcontroler);
				}
		}
		model.addListener(mlist);
	}
});

console.log('=============== >  tapControler.js ');



jQuery.extend({
	/**
	 * Example of job description returned by /runasyncjob
	{
	    "session": "45893CC5983E62F0A7AAB426228CB327",
	    "status": {
	        "job": {
	            "startTime": "2013-11-26T11:09:03.326+0100",
	            "results": null,
	            "jobId": "64_CATALOGUE",
	            "quote": {
	                "nil": true
	            },
	            "ownerId": "850C078372883BAAF323A88BD5FF3C77",
	            "destruction": {
	                "nil": true
	            },
	            "executionDuration": 0,
	            "parameters": {
	                "parameter": [
	                    {
	                        "id": "query",
	                        "$": "SELECT  TOP 100  *\nFROM CATALOGUE.CatalogueEntry\n"
	                    },
	                    {
	                        "id": "request",
	                        "$": "doQuery"
	                    },
	                    {
	                        "id": "lang",
	                        "$": "ADQL"
	                    }
	                ]
	            },
	            "runId": "TapHandle-127.0.0.1",
	            "errorSummary": {
	                "nil": true
	            },
	            "endTime": {
	                "nil": true
	            },
	            "phase": "EXECUTING"
	        }
	    },
	    "treepath": {
	        "schema": "CATALOGUE",
	        "nodekey": "saadatap",
	        "table": "CatalogueEntry",
	        "jobid": "64_CATALOGUE"
	    }
	}
	 */
	JobModel: function(description){
		/**
		 * who is listening to us?
		 */
		var listeners = new Array();
		var that = this;

		var jobDescription=null;
		var sessionId=null;
		var treePath=null;
		var id =null  ;
		var phase=null;
		var query =null;
		var countDown = 10;
		var progressTimer = null;
		var actions  = new Array();
		actions['COMPLETED'] = ["Actions", "Show Query", "Display Result", "Download Result", "Add to Cart", "Send to SAMP", /*"Add to Goodies",*/ "Summary"];
		actions['PENDING']   = ["Actions", "Show Query", "Run",  "Summary"];
		actions['EXECUTING'] = ["Actions", "Show Query", "Summary"];
		actions['QUEUED']    = ["Actions", "Show Query", "Summary"];
		actions['PENDED']    = ["Actions", "Show Query", "Summary"];
		actions['ERROR']     = ["Actions", "Show Query", "Edit Query", "Summary"];

		this.addListener = function(list){
			listeners.push(list);
		};

		this.setDescription = function(description){
			jobDescription = description;
			sessionId = description.session;
			treePath  = description.treepath;
			id        = description.status.job.jobId;
			phase     = description.status.job.phase;
			var parameter = description.status.job.parameters.parameter;
			for( var i=0 ; i<parameter.length ; i++) {
				var pi = parameter[i];
				if( pi.id == "query" || pi.id == "QUERY") {
					query = pi["$"];
					break;
				}
			}
		};
		this.setJobStatus = function(description){
			jobDescription.status = description.status;
			id        = description.status.job.jobId;
			phase     = description.status.job.phase;
		};

		this.setDescription(description);

		this.initForm = function(attributesHandlers) {
			that.notifyIsInit(attributesHandlers);
		};
		this.setOnError = function() {
			phase = 'ERROR';
			that.notifyUpdated();		
		};
		this.getPhase = function() {
			return  phase;
		};	

		this.updateStatus = function() {
			$.getJSON("jobsummary", {jsessionid: sessionID, NODE: treePath.nodekey, JOBID: id}, function(jsondata) {
				if( Processing.jsonError(jsondata, "Cannot get summary of job " + id) ) {
					return;
				}
				that.setJobStatus(jsondata);
				that.notifyUpdated();
			});		
		};

		this.checkJobCompleted = function() {
			Processing.show("Waiting for the query result " + countDown  + " (" + phase + ")");
			this.updateStatus();
			if( phase == 'COMPLETED' ) {
				Processing.hide();
				countDown = 0;
				ViewState.fireSubmitOK(dataTreeView.treePath);
			} else  if( phase == 'EXECUTING' || phase == 'QUEUED' || phase == 'PENDED'){
				if( countDown > 0 ) {
					countDown --;
					setTimeout(function(){ that.checkJobCompleted(); }, 1000);
				} else {
					Processing.hide();					
					progressTimer = true;
					setTimeout(function(){ that.checkJobProgress(); }, 5000);
				}
			} else  if( phase == 'ERROR'){
				countDown = 0;
				Processing.hide();				
				Modalinfo.error("\nMESSAGE:\n\n" + jobDescription.status.job.errorSummary.message, treePath.nodekey + ' job ' + treePath.jobid + " failed");
			} else {
				Processing.hide();
			}
		};

		this.checkJobProgress = function() {					
			if( progressTimer ) {
				this.updateStatus();
				if( phase == 'EXECUTING' || phase == 'QUEUED' || phase == 'PENDED'){
					progressTimer = setTimeout(function(){ that.checkJobProgress(); }, 5000);
				} 
			}
		};
		/*
		 * User actions on the job
		 */
		this.processJobAction= function(action) {
			Out.info("Process job action: '" + action + "'");
			if( action == 'Show Query') {	
				this.showQuery();				
			} else if( action == 'Summary') {	
				this.showSummary();				
			} else if( action == 'Display Result') {		
				ViewState.fireRecallOK(jobDescription.treepath, query);
			} else if( action == 'Download Result') {			
				this.downloadVotable();
			} else if( action == 'Add to Cart') {			
				cartView.fireAddJobResult(jobDescription.treepath, id);
			} else if( action == 'Send to SAMP') {	
				this.sampBroadcast();			
			} else if( action == ' "Add to Goodies' ){
			} else {
				this.displayResult();
			}
		};
		this.displayResult = function() {
			Processing.show("Get result of job JOB " + id);			
			$.getJSON("jobresult" , {jsessionid: sessionId, NODE: treePath.nodekey, JOBID: id, FORMAT: 'json'}, function(jsondata) {
				Processing.hide();
				if( Processing.jsonError(jsondata, "Cannot get result of job " + id) ) {				
					ViewState.fireRecallKO(jobDescription.treepath, query);
					that.setOnError();
					return;
				} else {
					//ViewState.fireRecallOK(jobDescription.treepath);
					resultPaneView.showTapResult(treePath, id, jsondata, $('#' + id).data("AttributeHandlers") );
				}
			});					
		};		
		this.showSummary = function() {
			Modalinfo.infoObject(jobDescription);

			//Modalinfo.infoObject(jobDescription.status.job, 'Status of ' + treePath.nodekey + ' job ' + treePath.jobid);
		};

		this.showQuery = function() {
			var report  = "";
			report =query.replace(/\\n/g,'\n            ')+ "\n";
			Modalinfo.info(report, 'Query of job ' + treePath.nodekey + '.' + treePath.jobid);
		};
		this.downloadVotable= function() {
			var url = 'jobresult?NODE=' + treePath.nodekey.trim() + '&JOBID=' + id.trim() + '&jsessionid='+ sessionID;
			PageLocation.changeLocation(url);
		};
		this.sampBroadcast= function() {
			var url =  rootUrl + 'jobresult?NODE=' + treePath.nodekey.trim() + '&JOBID=' + id.trim() + '&jsessionid='+ sessionID;
			WebSamp_mVc.fireSendVoreport(url, "table.load.votable", url);
		};
		this.removeJob= function(){
			Out.info("remove job " + id);
			countDown = 0;
			tapView.fireRemoveJob( id);
			cartView.fireRemoveJobResult(treePath, id);
			progressTimer =  false;			
			$.post("killjob"
					, {jsessionid: sessionID, NODE: treePath.nodekey, JOBID: id}
					, function(jsondata, status) {	
						// Nothing is returned when everything is OK
						if( jsondata == undefined || jsondata == null ) {
							$('#' +  id).remove();		
							return;
						} else if( Processing.jsonError(jsondata, "Cannot delete job: " +id) ) {
							return;
						} else {
							$('#' +  id).remove();		
						}
					});
		};
		/*
		 * Listener notifications
		 */
		this.notifyIsInit = function() {
			$.each(listeners, function(i){
				listeners[i].isInit(jobDescription, actions[phase]);
			});
		};
		this.notifyUpdated = function() {
			$.each(listeners, function(i){
				listeners[i].isUpdated(jobDescription, actions[phase]);
			});
		};
	}
});
console.log('=============== >  jobModel.js ');

jQuery.extend({
	/**
	 * Example of job description returned by /runasyncjob
	{
	    "session": "45893CC5983E62F0A7AAB426228CB327",
	    "status": {
	        "job": {
	            "startTime": "2013-11-26T11:09:03.326+0100",
	            "results": null,
	            "jobId": "64_CATALOGUE",
	            "quote": {
	                "nil": true
	            },
	            "ownerId": "850C078372883BAAF323A88BD5FF3C77",
	            "destruction": {
	                "nil": true
	            },
	            "executionDuration": 0,
	            "parameters": {
	                "parameter": [
	                    {
	                        "id": "query",
	                        "$": "SELECT  TOP 100  *\nFROM CATALOGUE.CatalogueEntry\n"
	                    },
	                    {
	                        "id": "request",
	                        "$": "doQuery"
	                    },
	                    {
	                        "id": "lang",
	                        "$": "ADQL"
	                    }
	                ]
	            },
	            "runId": "TapHandle-127.0.0.1",
	            "errorSummary": {
	                "nil": true
	            },
	            "endTime": {
	                "nil": true
	            },
	            "phase": "EXECUTING"
	        }
	    },
	    "treepath": {
	        "schema": "CATALOGUE",
	        "nodekey": "saadatap",
	        "table": "CatalogueEntry",
	        "jobid": "64_CATALOGUE"
	    }
	}
	 */

	JobView: function(){
		/**
		 * keep a reference to ourselves
		 */
		var that = this;
		/**
		 * who is listening to us?
		 */
		var listeners = new Array();
		/**
		 * Container of the job info
		 */
		var jobDiv = null;
		/**
		 * add a listener to this view
		 */
		this.addListener = function(list){
			listeners.push(list);
		};


		this.fireInitForm = function(containerID, attributesHandlers) {
			this.containerID = containerID;
			$.each(listeners, function(i){
				listeners[i].controlInitForm(attributesHandlers);
			});
		};
		this.fireJobAction = function(action){
			$.each(listeners, function(i){
				listeners[i].controlJobAction(action);
			});			
		};

		this.fireUpdateStatus = function() {
			$.each(listeners, function(i){
				listeners[i].controlUpdateStatus();
			});
		};
		this.fireSetOnError = function() {
			$.each(listeners, function(i){
				listeners[i].controlSetOnError();
			});
		};
		this.fireCheckJobCompleted = function() {
			$.each(listeners, function(i){
				return listeners[i].controlCheckJobCompleted();
			});
		};
		this.fireRemoveJob = function() {
			$.each(listeners, function(i){
				return listeners[i].controlRemoveJob();
			});
		};
		this.getId = function() {
			return id;
		};
		this.fireGetPhase = function() {
			var retour = false;
			$.each(listeners, function(i){
				retour = listeners[i].controlGetPhase();
			});		
			return retour;
		};

		this.initForm = function(jobDescription, actions, attributesHandlers){
			Out.info("init job form " + JSON.stringify(jobDescription.treepath) + " "  + jobDescription.session);		
//			var jtreePath = $.extend({}, treepath);
//			jtreePath.jobid = id;
			var nodekey  = jobDescription.treepath.nodekey;
			var id       = jobDescription.status.job.jobId;
			var phase    = jobDescription.status.job.phase;
			$('#' + that.containerID).prepend("<div id=" + id + " style='float: none;'></div>");
			$('#' + id).data("AttributeHandlers", attributesHandlers);
			$('#' + id).html('');
			//	$('#' + id).data('treepath', treepath);
			$('#' + id).append('<span id=' + id + '_id>' + nodekey + '.' 
					+ jobDescription.treepath.schema + '.' 
					+ jobDescription.treepath.table + ': job ' + id + '</span>');
			$('#' + id).append('&nbsp;<span id=' + id + '_phase class="' + phase.toLowerCase() + '">' + phase + '</span>');
			$('#' + id).append('<select id=' + id + '_actions style="font-size: small;"></select>');
			for( var i=0 ; i<actions.length ; i++ ) {
				$('#' + id + '_actions').append('<option value="' + actions[i] + '">' +  actions[i] + '</option>');
			}
			$('#' + id).append('<a id=' + id + '_close href="javascript:void(0);" class=closekw></a>');
			
			$('#' + id + "_actions").change(function() {
				that.fireJobAction ( $(this).val());
				$(this).val(0);
			});
			$('#' + id + "_close").click(function() {
				that.fireRemoveJob();
			});
			$("#taptab").tabs({
				selected: 3
			});
			jobDiv = $("#" + jobDescription.status.job.jobId);
		};

		this.updateForm = function(jobDescription, actions){
			var id       = jobDescription.status.job.jobId;
			var phase    = jobDescription.status.job.phase;
			var status = $('#' + id + '_phase');
			Out.info("update form for jobh " + id);
			status.attr("class", phase.toLowerCase());
			status.text(phase);
			var actionMenu = $('#' + id + '_actions');
			actionMenu.html('');
			for( var i=0 ; i<actions.length ; i++ ) {
				actionMenu.append('<option value="' + actions[i] + '">' +  actions[i] + '</option>');
			}
//			if( phase == 'ERROR' ) {
//				Processing.hide();
//				Modalinfo.info("Job " + treepath.nodekey + ">"  + id + " failed. See the job summary to get more details.");
//			}
		};
		
		this.setSelected = function(select) {
			if( select )
				jobDiv.css("background-color", "whitesmoke");
			else 
				jobDiv.css("background-color", "transparent");
		};
		
	}
});
console.log('=============== >  jobView.js ');

jQuery.extend({

	JobControler: function(model, view){
		/**
		 * listen to the view
		 */
		var vlist = {
				controlInitForm : function(attributesHandlers){
					model.initForm(attributesHandlers);
				},
				controlJobAction : function(action){
					model.processJobAction(action);
				},
				controlUpdateStatus : function(){
					model.updateStatus();
				},
				controlGetPhase: function(){
					return model.getPhase();
				},
				controlSetOnError: function(){
					return model.setOnError();
				},
				controlCheckJobCompleted: function(){
					return model.checkJobCompleted();
				},
				controlRemoveJob: function(){
					return model.removeJob();
				}
				
		};
		view.addListener(vlist);

		var mlist = {
				isInit : function(jobsDescription, actions){
					view.initForm(jobsDescription, actions);
				},
				isUpdated : function(jobsDescription, actions){
					view.updateForm(jobsDescription, actions);
				}
		};
		model.addListener(mlist);
	}
});

console.log('=============== >  jobControler.js ');

/*
 * 
 * Generated by http://www.wordconstructor.com/index.php based on  words nicequery and mydata
 */
	
var jobNames = ['nicerayge'
                , 'nicequury'
                , 'nicatuere'
                , 'nicequesy'
                , 'nicaqeary'
                , 'nicequigo'
                , 'titequori'
                , 'nocaqeeny'
                , 'nicotuery'
                , 'noneruery'
                , 'ticequery'
                , 'cicotairy'
                , 'nicequeca'
                , 'narenuemy'
                , 'tedoquiro'
                , 'ninoquary'
                , 'notequepe'
                , 'nicequisy'
                , 'nocequere'
                , 'nacaquery'
                , 'gicequere'
                , 'sifeqiery'
                , 'nimeniery'
                , 'nocoguely'
                , 'tidequery'
                , 'nicaqeady'
                , 'niceqiery'
                , 'geceraycy'
                , 'niceniere'
                , 'titoqoely'
                , 'casate'
                , 'becata'
                , 'binite'
                , 'wydata'
                , 'modana'
                , 'medane'
                , 'tadasy'
                , 'werata'
                , 'mydinu'
                , 'gumate'
                , 'melara'
                , 'tydere'
                , 'tedota'
               ];


/**
 * @returns the string of a random position in jobNames
 */
function getJobRootName() {
	return jobNames[Math.floor(Math.random()*(jobNames.length + 1))];

}
console.log('=============== >  jobDictionnary.js ');

jQuery.extend({

	CartView: function(jid){
		/**
		 * keep a reference to ourselves
		 */
		var that = this;

		/**
		 * datatables references
		 */
		var folderTables = new Array();
		/**
		 * who is listening to us?
		 */
		var listeners = new Array();
		/**
		 * add a listener to this view
		 */
		this.addListener = function(list){
			listeners.push(list);
		};

		this.fireAddJobResult = function(treepath, jobid) {
			Processing.show("Result of job " + treepath + "." + jobid + " added to the cart");
			$.each(listeners, function(i){
				listeners[i].controlAddJobResult(treepath, jobid);
			});
			this.resetJobControl();
			Processing.hide();
			};
		this.fireRemoveJobResult = function(nodekey, jobid) {
			$.each(listeners, function(i){
				listeners[i].controlRemoveJobResult(nodekey, jobid);
			});
			this.resetJobControl();
		};
		this.fireAddUrl = function(nodekey, url) {
			Processing.show("Data returned by " + url + " added to the cart");
			$.each(listeners, function(i){
				listeners[i].controlAddUrl(nodekey, url);
			});
			Processing.hide();
			this.resetJobControl();
		};
		this.fireRemoveUrl = function(nodekey, url) {
			$.each(listeners, function(i){
				listeners[i].controlRemoveUrl(nodekey, url);
			});
			this.resetJobControl();
		};
		this.fireRestrictedUrl = function(nodekey, url) {
			ModalInfo.info("Restricted Access", "Shopping cart facility does not support URL with a restricted access.");
		};
		this.fireOpenCart = function() {			
			$.each(listeners, function(i){
				listeners[i].controlOpenCart();
			});
		};
		this.fireCleanCart = function(tokens) {
			$.each(listeners, function(i){
				listeners[i].controleCleanCart(tokens);
			});
			this.resetJobControl();
		};
		this.fireStartArchiveBuilding = function() {
			$.each(listeners, function(i){
				listeners[i].controlStartArchiveBuilding();
			});
		};
		this.fireKillArchiveBuilding = function() {
			$.each(listeners, function(i){
				listeners[i].controlKillArchiveBuilding();
			});
		};
		this.fireArchiveDownload = function() {			
			$.each(listeners, function(i){
				listeners[i].controlArchiveDownload();
			});
		};
		this.fireGetJobPhase = function() {
			var retour=null;
			$.each(listeners, function(i){
				retour = listeners[i].controlGetJobPhase();
			});
			return retour;
		};
		this.fireChangeName = function(nodekey, dataType, rowNum, newName){
			$.each(listeners, function(i){
				listeners[i].controlChangeName(nodekey, dataType, rowNum, newName);
			});			
			this.resetJobControl();
		};
		this.resetJobControl= function() {
			Out.info("resetJobControl");
			$.each(listeners, function(i){
				listeners[i].controlResetZipjob();
			});			
			$('.zip').css("border", "0px");
			$('#detaildiv_download').attr("disabled", true);
			$('#detaildiv_submit').removeAttr("disabled");
			var jobspan = $('#cartjob_phase');
			jobspan.attr('class', 'nojob');
			jobspan.text('nojob');
			};
		
		this.fireCheckArchiveCompleted = function() {
			var phase = that.fireGetJobPhase();
			var jobspan = $('#cartjob_phase');
			jobspan.attr('class', phase.toLowerCase());
			jobspan.text(phase);
			if( phase == 'nojob') {
				$('.zip').css("border", "0px");
			}
			else if( phase == 'EXECUTING') {
				$('.zip').css("border", "2px solid orange");
				setTimeout("cartView.fireCheckArchiveCompleted();", 1000);
			}
			else if( phase == 'COMPLETED') {
				$('.zip').css("border", "2px solid green");
				$('#detaildiv_submit').attr("disabled", true);
				$('#detaildiv_download').removeAttr("disabled");
			}
			else {
				$('.zip').css("border", "2px solid red");
			}
		};

		this.initForm = function(cartData) {
			$('#detaildiv').remove();
			if ($('#detaildiv').length == 0) {
				$(document.documentElement).append(
				"<div id=detaildiv style='display: none;'></div>");
			}
			var empty = true;
			for( var nodekey in cartData) {
				empty = false;
				break;
			}			
			if( empty ) {
				Modalinfo.info("Empty Shopping Cart");
				return;
			}

			var table = '';
			//var phase = that.fireGetJobPhase();

			table += '<h2><img src="images/groscaddy.png"> Shopping Cart</h2>';
			table += '<div id=table_div></div>';
			table += "<h4 id=\"cartjob\" class='detailhead'> <img src=\"images/tdown.png\">Processing status</h4>";
			//table += '<br><span>Current Job Status</span> <span id=cartjob_phase class="' + phase.toLowerCase() + '">' + phase + '</span><BR>';
			table += '<br><span>Current Job Status</span> <span id=cartjob_phase class=""></span><BR>';
			table += "<span>Manage Content</span> <input type=button id=detaildiv_clean value='Remove Unselected Items'>";			
			table += "<input type=button id=detaildiv_cleanall value='Remove All Items'><br>";			
			table += "<span>Manage Job</span> <input type=button id=detaildiv_submit value='Start Processing'>";			
			table += "<input type=button id=detaildiv_abort value='Abort'><br>";			
			table += "<span>Get the Result</span> <input type=button id=detaildiv_download value='Download Cart' disabled='disabled'>";			

			$('#detaildiv').html(table);
			var modalbox = $('#detaildiv').modal();
			$("#simplemodal-container").css('height', 'auto'); 
			$("#simplemodal-container").css('width', 'auto'); 
			$(window).trigger('resize.simplemodal'); 

			this.setTableDiv(cartData);

			$('#detaildiv_clean').click( function() {
				var tokenArray =new Array();
				for( var i=0 ; i<folderTables.length ; i++) {
					tokenArray[tokenArray.length]  = $('input',folderTables[i].fnGetNodes()).serialize();
				}
				that.fireCleanCart(tokenArray);
				return false;
			} );
			$('#detaildiv_cleanall').click( function() {
				that.fireCleanCart("");
				modalbox.close();
				return false;
			} );
			$('#detaildiv_submit').click( function() {
				that.fireStartArchiveBuilding();
				return false;
			} );
			$('#detaildiv_abort').click( function() {
				that.fireKillArchiveBuilding();
				that.fireCheckArchiveCompleted();
				return false;
			} );

			$('#detaildiv_download').click( function() {
				that.fireArchiveDownload();
				$('.zip').css("border", "0px");
				return false;
			} );
			this.fireCheckArchiveCompleted();
		};
		
		this.setTableDiv= function(cartData) {
			folderTables = new Array();
			var table = '';
			var empty = true;
			for( var nodekey in cartData) {
				empty = false;
				break;
			}			
			if( empty ) {
				Modalinfo.info("Empty Shopping Cart");
				$.modal.close();
				return;
			}
			for( var nodekey in cartData) {
				table += "<h4 id=\"mappedmeta\" class='detailhead'> <img src=\"images/tdown.png\">Node  " + nodekey + " </h4>";
				table += "<div class='detaildata'>";
				table += "<table width=99% cellpadding=\"0\" cellspacing=\"0\" border=\"0\"  id=\"folder_" + nodekey +"\" class=\"display\"></table>";
				table += "</div>";
			}
			$('#table_div').html(table);
			for( var nodekey in cartData) {
				var folder = cartData[nodekey];
				var aaData = new Array();
				for( var i=0 ; i<folder.jobs.length ; i++) {
					aaData[aaData.length] = ["<INPUT TYPE=CHECKBOX checked name=\"" + nodekey + " job " + i + "\" value=" + i +">"
					                         , "Job", "<span>" + folder.jobs[i].name + "</span>", folder.jobs[i].uri];
				}
				for( var i=0 ; i<folder.urls.length ; i++) {
					aaData[aaData.length] = ["<INPUT TYPE=CHECKBOX checked name=\"" + nodekey + " url " + i + "\" value=" + i +">"
					                         ,  "URL", "<span>" + folder.urls[i].name + "</span>", folder.urls[i].uri];
				}
				folderTables[folderTables.length] = $('#folder_' + nodekey).dataTable(
						{
							"aoColumns" : [{sTitle: "Keep/Discard"}, {sTitle: "Data Source"},{sTitle: "Resource Name"},{sTitle: "Resource URI"}],
							"aaData" : aaData,
							"bPaginate" : false,
							"bInfo" : false,
							"aaSorting" : [],
							"bSort" : false,
							"bFilter" : false,
							"bAutoWidth" : true,
							"bDestroy": true
						});
				var oTable = folderTables[folderTables.length-1];
			    /* Apply the jEditable handlers to the table */
			    $('span', oTable.fnGetNodes()).editable( 
			    	function(data) {
			    		return data.replace(/[^\w]/g, "_");
			    		},
			    	{        
			    	 "callback": function( sValue, y ) {
				        var node = $(this).parent().get(0);
			            var aPos = oTable.fnGetPosition( node );
			            var row = aPos[0];
			            var type = oTable.fnGetData( row )[1];
			            /*
			             * jobs and urls are mixed in a table.
			             * We must retreive th position within the correct type
			             */
			            var cpt = -1;
			            for( var i=0 ; i<=row ; i++ ){
			            	if(  oTable.fnGetData( i )[1] == type) {
			            		cpt++;
			            	}
			            }
			            cartView.fireChangeName(nodekey, type, cpt, sValue);
			    	  },
			        "height": "1.33em", 
			        "width": "16em"}
			    );
			}	
		};
	}
});
console.log('=============== >  cartView.js ');

jQuery.extend({

	CartControler: function(model, view){
		/**
		 * listen to the view
		 */
		var vlist = {
				controlAddJobResult : function(treepath, jobid){
					model.addJobResult(treepath, jobid);
				},
				controlRemoveJobResult : function(treepath, jobid){
					model.removeJobResult(treepath, jobid);
				},
				controlAddUrl : function(treepath, url){
					model.addUrl(treepath, url);
				},
				controlRemoveUrl : function(treepath, url){
					model.removeJobUrl(treepath, url);
				},
				controlOpenCart : function(){
					model.notifyCartOpen();
				},
				controleCleanCart: function(tokens){
					model.cleanCart(tokens);
				},
				controlStartArchiveBuilding: function() {
					model.startArchiveBuilding();
				},
				controlKillArchiveBuilding: function() {
					model.killArchiveBuilding();
				},
				controlGetJobPhase: function() {
					return model.getJobPhase();
				},
				controlArchiveDownload: function() {
					return model.archiveDownload();
				},
				controlChangeName: function(nodekey, dataType, rowNum, newName) {
					model.changeName(nodekey, dataType, rowNum, newName);				
				},
				controlResetZipjob: function() {
					model.resetZipjob();				
				}

		};
		view.addListener(vlist);

		var mlist = {
				isCartCleaned : function(cartData){
					view.setTableDiv(cartData);
				},
				isInit : function(cartData){
					view.initForm(cartData);
				}
		};
		model.addListener(mlist);

	}
});

console.log('=============== >  cartControler.js ');

jQuery.extend({

	CartModel: function(nodekey, description){

		var listeners = new Array();
		var that = this;

		var cartData = {};
		var zipJob = null;
		/**
		 * add a listener to this view
		 */
		this.addListener = function(list){
			listeners.push(list);
		};
		this.addJobResult = function(treepath, jobid) {
			var nodekey = treepath.nodekey;
			var entry;
			if( (entry = cartData[nodekey]) == undefined ) {
				cartData[nodekey] = {jobs: new Array(), urls: new Array()};
				cartData[nodekey].jobs[0] = {name: jobid, uri: jobid};
			}
			else {
				var jobs = entry.jobs;
				for( var i=0 ; i<jobs.length ; i++ ) {
					if( jobs[i].uri == jobid ) {
						Modalinfo.info("Result of job " + nodekey + "." + jobid + " already in the cart", "input Error");
						return;
					}
				}
				cartData[nodekey].jobs[i] = {name: jobid, uri: jobid};			
			}
		};
		this.removeJobResult = function(treepath, jobid) {
			var nodekey = treepath.nodekey;
			var entry;
			if( (entry = cartData[nodekey]) == undefined ) {
				//Modalinfo.info("There is no data associated with node " + nodekey + " in the cart", "input Error");
			}
			else {
				var jobs = entry.jobs;
				for( var i=0 ; i<jobs.length ; i++ ) {
					if( jobs[i].uri == jobid ) {
						jobs.splice(i,1);
						if( jobs.length == 0 && entry.urls.length == 0 ) {
							delete cartData[nodekey];
						}
						return;
					}
				}
			//	Modalinfo.info("Job " + nodekey + "." + jobid+ " not found in the cart", "input Error");
			}			
		};
		this.addUrl = function(treepath, url) {
			var nodekey = treepath.nodekey;
			var entry;
//			var ch  = url.split("/");
//			var name = ch[ch.length - 1].replace(/[^\w]/, "_");
			var name = "preserve";
			if( (entry = cartData[nodekey]) == undefined ) {
				cartData[nodekey] = {jobs: new Array(), urls: new Array};
				cartData[nodekey].urls[0] = {name: name, uri: url};
			}
			else {
				var urls = entry.urls;
				for( var i=0 ; i<urls.length ; i++ ) {
					if( urls[i].uri == url ) {
						Modalinfo.info("This url of node " + nodekey  + " is already in the cart", "input Error");
						return;
					}
				}
				cartData[nodekey].urls[i] = {name: name, uri: url};			
			}			
		};
		this.removeUrl = function(treepath, url) {
			var nodekey = treepath.nodekey;
			var entry;
			if( (entry = cartData[nodekey]) == undefined ) {
				Modalinfo.info("There is no data associated with node " + nodekey + " in the cart", "input Error");
			}
			else {
				var urls = entry.urls;
				for( var i=0 ; i<urls.length ; i++ ) {
					if( urls[i].uri == url ) {
						urls.splice(i,1);
						if( urls.length == 0 && entry.jobs.length == 0 ) {
							delete cartData[nodekey];
						}		logger.debug("download " + zer.getUri() + " in " + fcopyName);

						return;
					}
				}
				//Modalinfo.info("URL not found in from the cart", "input Error");
			}						
		};
		this.cleanCart = function(tokenArray) {
			var old_cartData = cartData;
			cartData = {};
			for( var t=0 ; t<tokenArray.length ; t++ ) {
				var tokens = tokenArray[t];

				var tkList = tokens.split("&");
				for( var i=0 ; i<tkList.length ; i++ ){
					var row  = tkList[i].split('=');
					var num  = row[1];
					var key  = row[0].split('+');
					var node = key[0];
					if( key[1] == 'job' ) {
						that.addJobResult(node, (old_cartData[node]).jobs[num].uri);
					}
					else if( key[1] == 'url' ) {
						that.addUrl(node, (old_cartData[node]).urls[num].uri);
					}
				}
			}
			this.notifyCartCleaned();
		};

		this.changeName= function(nodekey, dataType, rowNum, newName) {
			if( dataType.toLowerCase() == "job" ) {
				cartData[nodekey].jobs[rowNum].name = newName;
			}
			else {
				cartData[nodekey].urls[rowNum].name = newName;
			}
			this.notifyCartCleaned();			
		};

		this.notifyCartOpen = function() {
			$.each(listeners, function(i){
				listeners[i].isInit(cartData);
			});                     
		};

		this.startArchiveBuilding = function() {
			$.ajax({
				type: 'POST',
				url: "datapack/zipper",
				data: {jsessionid: sessionID, PHASE: 'RUN', FORMAT: 'json',CART: JSON.stringify(cartData) },
				success: function(xmljob, status) {
					zipJob = new $.ZipjobModel(xmljob);
					setTimeout("cartView.fireCheckArchiveCompleted();", 1000);
				},
				dataType: "xml",
				error: function(xhr, textStatus, errorThrown) {
					Modalinfo.info("Archive building failed: Error " +  xhr.status  + "\n" +ajaxOptions + "\n" + thrownError);
				}
			});
		};

		this.killArchiveBuilding = function() {
			if( zipJob == null ) {
				return "nojob";
			}
			else {
				zipJob.kill();
				Out.info(zipJob.phase);
				return zipJob.phase;
			}
		};

		this.getJobPhase= function() {
			if( zipJob == null ) {
				return "nojob";
			}
			else {
				zipJob.refresh();
				return zipJob.phase;
			}
		};

		this.archiveDownload = function() {
			if( zipJob == null ) {
				Modalinfo.info("There is no active ZIP builder");
			}
			else {
				zipJob.download();
			}
		};

		this.notifyCartCleaned = function() {
			$.each(listeners, function(i){
				listeners[i].isCartCleaned(cartData);
			});			
		};
		
		this.resetZipjob = function() {
			Out.info("Reset Zipjob");
			zipJob = null ;
		};
	}
});

console.log('=============== >  cartModel.js ');

jQuery.extend({

	ZipjobModel:function(xmlSummary){
		/**
		 * keep a reference to ourselves
		 */
		var that = this;
		/*
		 * Job description params
		 */
		var xmlRoot='';
		var jobId='';
		var phase='';
		var params='';
		var results='';

		this.init = function(xmlSummary) {
	       // Out.info((new XMLSerializer()).serializeToString(xmlSummary));
	        /*
	         * The pair Chrome 15 and after and Jquery 1.7 do not support NS in XML
	         * parsing. We must feed the find() function selector including both NS an no NS filed names
	         */
			var xmlRoot = $(xmlSummary).find('uws\\:job, job');
			this.jobId = xmlRoot.find('uws\\:jobId, jobId').text();
			this.phase = xmlRoot.find('uws\\:phase, phase').text();
			this.params = new Array();
			xmlRoot.find("uws\\:parameters, parameters").find("uws\\:parameter, parameter").each(function() {
				that.params[$(this).attr("id")] = $(this).text();
			});	
			that.results = new Array();
			xmlRoot.find("uws\\:results, results").find("uws\\:result, result").each(function() {
				that.results[that.results.length] = $(this).attr("xlink:href");
			});
		};
		
		that.init(xmlSummary);

		this.kill = function() {
			$.ajax({
			    data: {jsessionid: sessionID},
				type: 'DELETE',
			    dataType: "xml",
				url: "datapack/zipper/" + that.jobId,
				success: function(xmljob, status) {
					Modalinfo.info("Job " +  that.jobId + " killed");
					//that.refresh();
				},
				error: function(xhr, ajaxOptions, thrownError) {
					Modalinfo.info("Zipjob kill failed: Error " +  xhr.status + "\n" + xhr  + "\n" +ajaxOptions + "\n" + thrownError);
				}
			});
		};
		this.refresh = function() {
			$.ajax({
			    data: {jsessionid: sessionID},
			    dataType: "xml",
				type: 'GET',
				url: "datapack/zipper/" + that.jobId,
				success: function(xmljob, status) {Out.info("refresh cart job success");that.init(xmljob);},
				error: function(xhr, ajaxOptions, thrownError) {
					Modalinfo.info("Zipjob refresh failed: Error " + xhr.status + "\n" + xhr  + "\n" +ajaxOptions + "\n" + thrownError);
				}
			});
//			$.get("datapack/zipper/" + that.jobId
//				, function(data) {that.init(data);}
//			    , "xml") ;
		};
		this.download = function() {
			if( that.results.length >= 1 ) {
				var url = that.results[0];
				PageLocation.changeLocation(url);
			} else {
				Modalinfo.info("No ZIP archive available");
			}
 		};
	}
});
console.log('=============== >  zipjobModel.js ');

function DataTreeView() {
	/**
	 * default node list
	 * The nodes given here must be initialized in Nodebase.java
	 */
	this.nodeList = new Array();
	/**
	 * Metadata of the data files referenced in the Goodies node
	 */
	this.goodies = new Array();
	this.treePath = null;
	this.capabilities = null;
	this.info = null;
}
DataTreeView.prototype = {
		initNodeBase : function(){
			var that = this;
			Processing.show("Fetching available nodes");
			$(".logo").attr("class", "logourbana");
			$.ajax({
				async: false,
				type: 'GET',
				dataType: 'json',
				url: "availablenodes",
				error: function() {
					Processing.hide();
					Modalinfo.error("availablenodes failure") ;
				},
				success: function(data) {
					Processing.hide();
					$("title").text("TapHandle " + data.version)
					sessionID = data.sessionID;
					for( var i=0 ; i<data.nodes.length ; i++) {
						that.nodeList[that.nodeList.length] = {
								id   :  data.nodes[i].key
								, text : data.nodes[i].key+ ' [' + data.nodes[i].description + ']'
								, ivoid: data.nodes[i].ivoid 
								, url: data.nodes[i].url
								, description: data.nodes[i].description
								, extra: "<br>" +data.nodes[i].url + "<br>" +data.nodes[i].ivoid + "<br>"};
					}
					$('input#node_selector').jsonSuggest(
							{data: that.nodeList
								, minCharacters: 0
								, onSelect: function(data){
									var key = $('#node_selector').val().split(' ')[0];
									that.fireNewNodeEvent(key);
									$('#node_selector').val(key);
								}
							});
					setTimeout('$(".logourbana").attr("class", "logo")', 2000);
				}
			});
		},

		fireNewNodeEvent: function(nodekey) {
			var that = this;
			Processing.show("Waiting on " + nodekey + " node description");

			$.getJSON("getnode", {jsessionid: sessionID, node: nodekey }, function(jsdata) {
				Processing.hide();
				if( Processing.jsonError(jsdata, "Cannot make data tree") ) {
					return;
				} else {
					Processing.show("Building node");
					that.fireBuildTree(jsdata);
					Processing.hide();
				}
			});
		},
		fireBuildTree: function(jsdata) {
			this.capabilities = {supportSyncQueries: true
					, supportAsyncQueries: (jsdata.asyncsupport == "true")?true: false
							, supportJoin: true
							, supportUpload:(jsdata.uploadsupport == "true")?true: false};
			this.info = {url: jsdata.nodeurl , ivoid: null, description: "Not available"};
			$("div#treedisp").jstree("remove","#" + jsdata.nodekey);
			/*
			 * Create the root of the subtree of this node
			 */
			var description="No description available";
			for( var i=0 ; i<this.nodeList.length ; i++ ) {
				var n = this.nodeList[i];
				if( n.id ==  jsdata.nodekey) {
					this.info.ivoid = n.ivoid;
					this.info.description = n.description;
					description = jsdata.nodeurl + "\n" + n.ivoid + "\n" + n.description + "\n" ;
					break;
				}
			}
			description += "\n- Asynchronous mode  " + ((!this.capabilities.supportAsyncQueries)?" not ": "") + "supported\n";
			description += "- Table upload " + ((!this.capabilities.supportUpload)?" not ": "") + "supported\n";
			$("div#treedisp").jstree("create"
					, $("div#treedisp")
					, false
					, {"data" : {"icon": "images/Database.png", "attr":{"id": jsdata.nodekey, "title": description}, "title" : jsdata.nodekey},
						"state": "closed"}
					,false
					,true);       
			/*
			 * Create first the first level tree (schemas)
			 */
			for( var i=0 ; i<jsdata.schemas.length ; i++ ) {
				var id_schema = jsdata.nodekey + "X" + jsdata.schemas[i].name;
				var description = jsdata.schemas[i].description;

				var schemaName = jsdata.schemas[i].name;				
				if(schemaName.match(/TAP_SCHEMA/i) ) {
					icon = "images/Redcube.png";
					description = "Schema containing the description of the published tables";
				} else if(schemaName.match(/ivoa/i) ) {
					icon =  "images/Greencube.png";
					description = "Tables matching IVOA data models (e.g. ObsCore)";
				} else {
					icon =  "images/Bluecube.png";
					if( description == "") {
						description = "No Description Available";
					}
				}
				$("div#treedisp").jstree("create"
						, $("#" + jsdata.nodekey)
						, false
						, {"data" : {"icon": icon, "attr":{"id": id_schema, "title": description}, "title" : jsdata.schemas[i].name},
							"state": "closed",
							"attr" :{"id": id_schema}}
						,false
						,true);       
			}
			/*
			 * add leaves (tables) the the schemas
			 */
			var trunc = new Array();
			for( var i=0 ; i<jsdata.schemas.length ; i++ ) {
				var schema = jsdata.schemas[i];
				var id_schema = jsdata.nodekey + "X" + schema.name;
				var nb_tables = 0;
				var root = $("#" + id_schema);
				for( var j=0 ; j<schema.tables.length ; j++ ) {
					var table = schema.tables[j];
					var id_table = jsdata.nodekey + ";" + schema.name + ";" + table.name;
					var description = table.description;
					if( description == "") {
						description = "No Description Available";
					}
					$("div#treedisp").jstree("create"
							, root
							, false
							, {"data"  : {"icon": "images/SQLTable.png", "attr":{"id": id_table, "title": description}, "title" : table.name},
								"state": "closed",
								"attr" : {"id": id_table}
							}
							,false
							,true);   
					if( (nb_tables++) > 20 ) {
						trunc[trunc.length] = schema.name;
						break;
					}
				}
			}
			$( "div#treedisp").jstree('close_all', -1);	
			var msg = "";
			if(jsdata.truncated != null  ) {
				msg = "TRUNCATED TABLE LIST: The table list has been truncated by the server (~100 tables)";
			} 
			if( trunc.length > 0 ) {
				msg += "\nTRUNCATED SCHEMA: The table list of following schemas [" + trunc.join(",") + "] have been truncated to 20 items";
			}
			if( msg != "" ) {
				Modalinfo.info(msg + "\n\nDouble click on the '" + jsdata.nodekey + "' node to make you own selection");
			}
		},

		fireTreeNodeEvent:function(dataTreePath, andsubmit) {
			ViewState.fireDoubleClickOK(dataTreePath);
			tapView.fireTreeNodeEvent(dataTreePath, andsubmit);	
		},
		/**
		 * jsdata: {nodekey: ... , table: ...}
		 */
		addGoodies: function(jsdata){
			var id_schema = "GoodiesX" + jsdata.nodekey;

			if( $("#" + id_schema).length == 0 ){
				$("div#treedisp").jstree("create"
						, $("#goodies")
						, false
						, {"data" : {"attr":{"id": id_schema, "title": "description"}, "title" : jsdata.nodekey},
							"state": "closed",
							"attr" :{"id": id_schema},

						}
						, false
						, true); 
			} else 	if( $("#" + id_schema + " #" + jsdata.table).length != 0 ){
				Modalinfo.error( "Node " + jsdata.nodekey + "." + jsdata.table + " already exist" );
				return;
			}
			if( $("#" + id_schema + " #" + jsdata.table).length == 0 ){
				$("div#treedisp").jstree("create"
						, $("#" + id_schema)
						, false // position
						, {"data"  : {"icon": "images/SQLTable.png", "attr":{"id": jsdata.table, "title": "description"}, "title" : jsdata.table},
							"state": "closed"
						}
						,false// callback
						,true //skip rename
				);   
			}
			return;
		},
		/**
		 * jsdata: {nodekey: ... , table: ...}
		 */
		delGoodies: function(jsdata){
			var id_schema = "GoodiesX" + jsdata.nodekey;

			if( $("#" + id_schema).length != 0 ){
				$("div#treedisp").jstree("remove", $("#" + id_schema + " #" + jsdata.table));
				if($("#" + id_schema).find("> ul > li:eq(0)").length == 0) {
					$("div#treedisp").jstree("remove", $("#" + id_schema));
				}
			}
			return;
		},
		pushJobToGoodies: function(jid){
			var that = this;
			Processing.show("Pushing job to goodies");
			$.getJSON("pushjobtogoodies"
					, {jsessionid: sessionID, node: "table", jobid:"jobid" , goodiesname: "goodiesname" }, function(jsondata) {
						Processing.hide();
						if( Processing.jsonError(jsondata, "Cannot get meta data") ) {
							return;
						}
						that.addGoodies(jsondata);
					});
		},
		uploadFile: function() {
			Modalinfo.dataPanel(title			
					, '<form id="uploadPanel" target="_sblank" action="uploaduserposlist" method="post"'
					+  'enctype="multipart/form-data">'
					+  ' <input class=stdinput  id="uploadPanel_filename" type="file" name="file" /><br>'
					+ '  <p class=help></p><br>'
					+  ' <input  type="submit" value="Upload" />'
					+  ' </form>'
					, null);
			$('#uploadPanel p').html("description");
			$('form#uploadPanel').ajaxForm({
				beforeSubmit: function() {
					if(beforeHandler != null ) {
						beforeHandler();
					}
				},
				success: function(e) {
					Modalinfo.close();
					if( Processing.jsonError(e, "Upload Position List Failure") ) {
						return;
					} else {
						/*
						 * Must add a goodies node here
						 */
						Out.debug("Upload success: " + JSON.stringify(e));
						if( handler != null) {
							var retour = {retour: e, path : $('#uploadPanel_filename').val().xtractFilename()};
							handler(retour);
						}
					}
				}
			});
		},
		setTitlePath: function (treepath) {
			Out.info("title " + treepath);
			this.treePath = treepath;
			var tp = $('#titlepath');
			var span = '<span style="font-style: normal; color: #888;font-size: x-small ; background-color:';
			tp.html('');
			if( treepath) {
				var job = ( !treepath.jobid || treepath.jobid == "")? "": '&gt;'+ treepath.jobid;
				tp.append(span
						+ ((this.capabilities.supportSyncQueries== true)?'lightgreen': 'salmon') 
						+ ';" title="' + ((this.capabilities.supportSyncQueries== true)?'S': 'Does not s') + 'upport synchronous queries">S</span>');
				tp.append(span
						+ ((this.capabilities.supportJoin== true)?'lightgreen': 'salmon')
						+ ';" title="' + ((this.capabilities.supportJoin== true)?'S': 'Does not s')+ 'upport ADQL joins">J</span>');
				tp.append(span
						+ ((this.capabilities.supportAsyncQueries == true)?'lightgreen': 'salmon') 
						+ ';" title="' + ((this.capabilities.supportAsyncQueries == true)?'S': 'Does not s')+ 'upport asynchronous queries">A</span>');
				tp.append(span
						+ ((this.capabilities.supportUpload == true)?'lightgreen': 'salmon') 
						+ ';" title="' + ((this.capabilities.supportUpload == true)?'S': 'Does not s')+ 'upport table upload">U</span>');
				tp.append('<a href="#" style="font-style: normal; font-size: x-small ; background-color: lightblue;" title="Click to get more info" onclick="dataTreeView.showNodeInfos();"> ? </a>');
				tp.append('&nbsp;<i>' + treepath.nodekey + '&gt;' + treepath.schema + '&gt;'+ treepath.table+ job);
			}
		},
		showNodeInfos: function () {
			var report = {"info": this.info, "capabilities": this.capabilities};
			Modalinfo.infoObject(report, "Node " + this.treePath.nodekey);
		},
		getBookmark: function() {
			var np = window.location.href.split('?')[0].replace(/\/$/, "");;
			return (this.info != null)?np + "?url=" + escape(this.info.url): np;
		}
};


console.log('=============== >  datatreeView.js ');

function initFunctions () {
	this.initMVC = function() {
		var resultPaneModel      = new $.ResultPaneModel();
		resultPaneView           = new $.ResultPaneView();
		new $.ResultPaneController(resultPaneModel, resultPaneView);

		var tapModel       = new $.TapModel();
		tapView            = new $.TapView();
		new $.TapController(tapModel, tapView);

		var cartModel       = new $.CartModel();
		cartView            = new $.CartView();
		new $.CartControler(cartModel, cartView);

		var nodeFilterModel       = new $.NodeFilterModel();
		nodeFilterView            = new $.NodeFilterView();
		new $.NodeFilterController(nodeFilterModel, nodeFilterView);

		dataTreeView = new DataTreeView();

	};

	this.initLayout = function() {
		$.alerts.overlayOpacity = 0.5;
		$.alerts.overlayColor = '#000';
		/*
		 * layout plugin, requires JQuery 1.7 or higher
		 * Split the bottom div in 3 splitters divs.
		 */		
		layoutPane = $('#accesspane').layout();
		layoutPane.sizePane("south", "10%");
	};

	this.initNodeAccess = function() {
		dataTreeView.initNodeBase();
		adqlQueryView = QueryConstraintEditor.adqlTextEditor({ parentDivId: 'taptext', defaultQuery: ''});
		MetadataSource.init({getMetaTable: "gettableatt", getJoinedTables: "gettablejoinkeys", getUserGoodie: null});
		
		tapColumnSelector = QueryConstraintEditor.tapColumnSelector({parentDivId:'tapselect'
			, formName: 'tapFormColName'
			, queryView: adqlQueryView});
			
		tapConstraintEditor = QueryConstraintEditor.tapConstraintEditor({parentDivId: 'tapwhere'
			, formName: 'tapFormName'
			, sesameUrl:"sesame"
			, upload: {url: "uploadposlist", postHandler: function(retour){alert("postHandler " + retour);}}
			, queryView: adqlQueryView});


		$("input#node_selector").keypress(function(event) {
			if (event.which == '13') {
				dataTreeView.fireNewNodeEvent($('#node_selector').val());
			}
		});
	};

	this.initDataTree = function() {
		dataTree = $("div#treedisp").jstree({
			"json_data"   : {"data" : [ {  "attr"     : { "id"   : "rootid", "title": "Repository for uploaded tables (Not implemented yet)" },
				"data"        : { "title"   : "Goodies" , "attr": {"id": "goodies"}}}]}  , 
				"plugins"     : [ "themes", "json_data", "dnd", "crrm"],
				"rules" : {"deletable" : "all"},
				"dnd"         : {"drop_target" : "#resultpane,#taptab,#showquerymeta",
			    "drop_finish" : function (data) {
						var parent = data.r;
						var id = data.o.attr("id");
						var streepath = null; ;
						if( id == null || (streepath = data.o.attr("id").split(';')).length < 3 ) {
							Modalinfo.info("Meta data only available for tables: ("  +  streepath + ")", 'User Input Error');
						}
						else {
							var treePath = {nodekey: streepath[0]
							, schema: streepath[1]
							, tableorg: streepath[2]
							, table: streepath[2].split('.').pop()};
							var s = streepath[2].split('.').shift();
							var p = streepath[2].indexOf(".");
							if( p > -1 ) s = streepath[2].substring(p+1);
							else s = streepath[2]
							var treePath = {nodekey: streepath[0]
							, schema: streepath[1]
							, tableorg: streepath[2]
							, table: s};
							while(parent.length != 0  ) {
								if(parent.is('#resultpane') ) {
									ViewState.fireDoubleClickOK(treePath);
									return;
								} else if(parent.attr('id') == "showquerymeta" ) {
									resultPaneView.fireShowMetaNode(treePath);	
									return;
								} else if(  parent.attr('id') == "taptab") {
									ViewState.fireDragOnQueryForm(treePath);
									return;
								}
								parent = parent.parent();
							}
						}
					}
				},
				// Node sorting by DnD blocked
				"crrm" : {"move" : {"check_move" : function (m) {return false; }}
				}
		}); // end of jstree

		dataTree.bind("dblclick.jstree", function (e, data) {
			var node = $(e.target).closest("li");
			var id = node[0].id; //id of the selected node					
			var treePath = id.split(';');
			if( treePath.length == 1 ) {
				var nm = $(e.target).closest("a")[0].id;
				if( nm != "" ) {
					nodeFilterView.fireOpenSelectorWindow(nm);
				}
			} else if( treePath.length < 3 ) {
				Modalinfo.info("Query can only be applied on one data category or one data class: ("  +  treePath + ")", 'User Input Error');
			} else {
				var fTreePath = {nodekey: treePath[0], schema: treePath[1], tableorg: treePath[2], table: treePath[2].split('.').pop() };
				var s = treePath[2].split('.').shift();
				var p = treePath[2].indexOf(".");
				if( p > -1 ) s = treePath[2].substring(p+1);
				else s = treePath[2]
				fTreePath = {nodekey: treePath[0]
				, schema: treePath[1]
				, tableorg: treePath[2]
				, table: s};

				ViewState.fireDoubleClickOK(fTreePath);
			}
		});
		rootUrl = "http://" + window.location.hostname +  (location.port?":"+location.port:"") + window.location.pathname;
		/*
		 * Connect the URL passed as parameter
		 */
		var defaultUrl  =  (RegExp('url=' + '(.+?)(&|$)').exec(location.search)||[,null])[1];
		if( defaultUrl != null ) {
			dataTreeView.fireNewNodeEvent(unescape(defaultUrl));
		}
		Out.setdebugModeFromUrl();
	};

	this.initQueryForm = function() {
		/*
		 * Activate submit buttons
		 */
		$('#submitquery').click(function() {
            _paq.push(['trackPageView', 'saada TapHandle/submit']);
			ViewState.fireSubmit();
			//resultPaneView.fireSubmitQueryEvent();
		});
		$("#qlimit").keyup(function(event) {
			if( $("#qlimit").val() == '' || $("#qlimit").val().match(/^[0-9]+$/) ) {
				adqlQueryView.fireAddConstraint("tap", "limit", [getQLimit()]);
			} else {
				Modalinfo.info('The result limit must be a positive integer value' , "User Input Error");
				$("#qlimit").val(100);
				adqlQueryView.fireAddConstraint("tap", "limit", [getQLimit()]);
				return false;
			}

		});

		/*********************************************************************************************
		 * Query form setup
		 */
		$("#taptab").tabs();
		$("#taptab").tabs({
			unselect : true
		});
		$("#tapconstraintlist").droppable({
			drop: function(event, ui){
				tapView.fireAttributeEvent(ui.draggable);		
			}
		});
		$("#tapselectlist").droppable({
			drop: function(event, ui){
				tapView.fireSelectEvent(ui.draggable);		
			}
		});
		$("#taporderby").droppable({
			drop: function(event, ui){
				tapView.fireOrderByEventEvent(ui.draggable);		
			}
		});
		$("#tapalpha").droppable({
			drop: function(event, ui){
				tapView.fireAlphaEvent(ui.draggable);		
			}
		});
		$("#tapdelta").droppable({
			drop: function(event, ui){
				tapView.fireDeltaEvent(ui.draggable);		
			}
		});
		/*
		 * Coordinates input
		 */
		$("#tapwhere input").keypress(function(event) {
			if (event.which == '13') {
				tapView.fireInputCoordEvent();
			}
		});
		/*
		 * Name resolver buton activation
		 */
		$(".sesame").click(function() {
			var inputfield = $(this).parents('div').find(".coordinputvalue");
			Processing.show("Waiting on SESAME response");
			$.getJSON("sesame", {object: inputfield.val() }, function(data) {
				Processing.hide();
				if( Processing.jsonError(data, "Sesame failure") ) {
					return;
				} else {
					inputfield.val(data.alpha + ' ' + data.delta);
				}
			});
		});
		/*
		 * This callback can be changed changed at everytime: do not use the "onclick" HTML  
		 * attribute which is not overriden by JQuery "click" callback
		 */
		$('#showquerymeta').click(function(){Modalinfo.info("No meta data available yet", 'Application not Ready');});
		tapView.fireRefreshJobList();
	}
	this.initSamp = function() {
		WebSamp_mVc.init("TAPHandle"
				, "http://saada.u-strasbg.fr/taphandle/images/tap64.png"
				, "Universal TAP service browser");
	}
};


var whenReady = new initFunctions ();
console.log('=============== >  initFunctions.js ');

/*
 * Various utility routines standing out of any MVC 
 */

function setTitlePath(treepath) {
	Out.info("title " + treepath);
	var job = (treepath.jobid == null)? "": '&gt;'+ treepath.jobid;
	$('#titlepath').html('<i>' + treepath.nodekey + '&gt;' + treepath.schema + '&gt;'+ treepath.table+ job);
}

function getQLimit() {
	var limit = 10;
	if( $("#qlimit").val().match(/^[0-9]*$/) ) {
		limit = $("#qlimit").val();
	}
	return limit;
}

function switchArrow(id) {
	var image = $('#'+id+'').find('img').attr('src');
	if (image == 'images/tdown.png') {
		$('#'+id+'').find('img').attr('src', 'images/tright.png');
	} else if (image == 'images/tright.png') {
		$('#'+id+'').find('img').attr('src', 'images/tdown.png');
	}
}

function quoteTableName(tableName){
	var regex = /([^.]*)\.(.*)/;
	var results = regex.exec(tableName);
	var table, schema;
	if(!results){
		table = tableName;
		schema = "";
	} else if( results.length == 2 ) {
		table = results[1]; 
		schema = "";

	} else  {
		table =  results[2];  
		schema = results[1] + ".";
	}
	if( table.match(/^[a-zA-Z0-9][a-zA-Z0-9_]*$/ ) ){
		return schema + table;
	} else {
		return schema + '"' + table +'"';
	}
}

console.log('=============== >  utils.js ');

/**
 * Jquery stuff initialisation
 * + Global variables declaration
 */
var downloadIFrameID = 'downloadIFrameID';

/*
 * The JEE session ID is used to keep session track through http params 
 */
var sessionID;
var rootUrl;
/*
 * View associated with specific functionality
 */
var resultPaneView;
var tapView ;
var dataTreeView ;
var cartView ;
var adqlQueryView ;
var tapColumnSelector;
var tapConstraintEditor;
/*
 * Using a Jquery bind() here has a strange behaviour...
 * http://stackoverflow.com/questions/4458630/unable-to-unbind-the-window-beforeunload-event-in-jquery
 */
//var authOK = false;
//window.onbeforeunload = function() {
//	if( !authOK) {
//		return  'WARNING: Reloading or leaving this page will lost the current session';
//	} else {
//		authOK = false;
//	}
//};
/*
 * Use true/false or 0/1 in ADQL?
 */
var booleansupported = false;
/*
 * JQuery object managing splitters
 */
var layoutPane;
/*
 * invoked once the page is loaded
 */
$().ready(function() {
	whenReady.initMVC();
	whenReady.initLayout();
	whenReady.initNodeAccess();
	whenReady.initDataTree();
	whenReady.initQueryForm();
	whenReady.initSamp();
	PageLocation.confirmBeforeUnlaod();
});


console.log('=============== >  ready.js ');

/**
 * This singleton make sure that all components of the interface are always
 * in a state which is compliant with the result of the last user action
 * Public method can be invoked from anywhere in the code
 * 
 * The dataTreePath parameter od all public method must be an instance of the class 
 * DataTreePath({nodekey:"node", schema: "schema", table: "table", tableorg: "tableorg"});
 * defined in domain.js
 */
ViewState = function() {
	
	/************************************
	 * Private method triggering action on the interface components
	 */
	/**
	 * Remove the page title
	 */
	var cleanPath = function(dataTreePath) {
		dataTreeView.setTitlePath(null);
	};
	/**
	 * Set the page title
	 */
	var setPath = function(dataTreePath) {
		dataTreeView.setTitlePath(dataTreePath);
	};
	/**
	 * Empty the data div
	 */
	var cleanData = function(dataTreePath) {
		resultPaneView.clearTapResult();
	};
	/**
	 * Display data
	 */
	var setData = function(dataTreePath) {
		tapView.fireDisplayResult(dataTreePath);
	};
	/**
	 * Empty the query form
	 */
	var cleanForm = function(dataTreePath) {
	};
	/**
	 * Init the query form
	 */
	var setForm= function(dataTreePath) {
		tapView.fireTreeNodeEvent(dataTreePath, false);	
	};
	/**
	 * Remove the query text
	 */
	var cleanQuery = function(dataTreePath) {
		adqlQueryView.displayQuery('');
	};
	/**
	 * Set the query text
	 */
	var setQuery= function(dataTreePath, query) {
		adqlQueryView.displayQuery(query);
	};
	
	/************************************************
	 * Public methods invoked on any user action implying 
	 * a change of the status interface.
	 */
	/**
	 * A double click on a node succeed
	 */
	var fireDoubleClickOK = function(dataTreePath) {
		/*
		 * Clean everything
		 */
		cleanPath(dataTreePath);
		cleanData(dataTreePath);
		cleanForm(dataTreePath);
		cleanQuery(dataTreePath);
		/*
		 * Connect the interface to the selected node
		 */
		setPath(dataTreePath);			
		tapView.fireTreeNodeEvent(dataTreePath, true);	
	};
	/**
	 * A double click on a node has generated an error
	 */
	var fireDoubleClickKO = function(dataTreePath) {
		cleanPath(dataTreePath);
		cleanData(dataTreePath);
		cleanForm(dataTreePath);
		cleanQuery(dataTreePath);
		setForm(dataTreePath);
		setPath(dataTreePath);
	};
	/**
	 * a data node has been dropped onto the query form
	 */
	var fireDragOnQueryForm = function(dataTreePath) {
		cleanPath(dataTreePath);
		cleanData(dataTreePath);
		cleanForm(dataTreePath);
		cleanQuery(dataTreePath);
		setForm(dataTreePath);
		setPath(dataTreePath);
		resultPaneView.fireExpandForm();

	};
	/**
	 * A new jobs has ben submitted
	 */
	var fireSubmit = function(dataTreePath) {
		cleanData(dataTreePath);
		tapView.fireSubmitQueryEvent();
	};
	/**
	 * A new jobs has ben submitted
	 */
	var fireSubmitted = function(dataTreePath, jobid) {
		cleanData(dataTreePath);
		dataTreePath.jobid = jobid;
		setPath(dataTreePath);
	};
	/**
	 * The current job succeed
	 */
	var fireSubmitOK = function(dataTreePath) {
		cleanData(dataTreePath);
		setData(dataTreePath);
	};
	/**
	 * The current job has not completed within 10"
	 */
	var fireSubmitDelayed = function(dataTreePath) {
		cleanData(dataTreePath);
	};
	/**
	 * The current job failed
	 */
	var fireSubmitKO = function(dataTreePath) {
		cleanData(dataTreePath);
		resultPaneView.fireExpandForm();
	};
	/**
	 * Recall a successful job
	 */
	var fireRecallOK = function(dataTreePath, query) {
		cleanPath(dataTreePath);
		cleanData(dataTreePath);
		cleanForm(dataTreePath);
		cleanQuery(dataTreePath);
		setForm(dataTreePath);
		setQuery(dataTreePath, query);
		setPath(dataTreePath);
		setData(dataTreePath);
	};
	/**
	 * Recall a failed job
	 */
	var fireRecallKO = function(dataTreePath, query) {
		cleanPath(dataTreePath);
		cleanData(dataTreePath);
		cleanForm(dataTreePath);
		cleanQuery(dataTreePath);
		setForm(dataTreePath);
		setQuery(dataTreePath, query);
		setPath(dataTreePath);
		resultPaneView.fireExpandForm();
	};
	
	/*
	 * exports
	 */
	var pblc = {};
	pblc.fireDoubleClickOK   = fireDoubleClickOK;
	pblc.fireDoubleClickKO   = fireDoubleClickKO;
	pblc.fireDragOnQueryForm = fireDragOnQueryForm;
	pblc.fireSubmit          = fireSubmit;
	pblc.fireSubmitted       = fireSubmitted;
	pblc.fireSubmitOK        = fireSubmitOK;
	pblc.fireSubmitDelayed   = fireSubmitDelayed;
	pblc.fireSubmitKO        = fireSubmitKO;
	pblc.fireRecallOK        = fireRecallOK;
	pblc.fireRecallKO        = fireRecallKO;
	return pblc;
}();

console.log('=============== >  viewState.js ');

function adjustWidths() {
	$('#superwrapper', '#header', '#datapane', '#formframe', '#footer', '#accesspane', '#formpane', '#resultpane').css("width", "auto");
	wWidth = $('window').width();

	$('#superwrapper').width(wWidth);
	$('#header').width(wWidth);
	$('#datapane').width(wWidth);
	$('#formframe').width(wWidth);
	$('#footer').width(wWidth);
	$('#accesspane').width(wWidth);

	$('#formpane').width(wWidth - $('#formpanemenu').width());
	$('#resultpane').width(wWidth - $('#treepane').width() - 6);
		
};


//function adjustHeights() {
//	$('#superwrapper', '#header', '#footer', '#accesspane').css("height", "auto");
//	wHeight = $('window').height();
//	if ($('#formframe').height() > 300) $('#formframe').height(300);
//	$('#superwrapper').height(wHeight);
//	$('#accesspane').height(wHeight - $('#header').height() - $('#footer').height());
//	
//};
	
	
$().ready(function() {
	adjustWidths();
//	adjustHeights();
	

	$(window).resize(function() {
		adjustWidths();
//		adjustHeights();
		
	});
		
});
console.log('=============== >  resize.js ');

